pax_global_header00006660000000000000000000000064141556422570014525gustar00rootroot0000000000000052 comment=72d1e84b7e8d0e838fef9b184a52f226b5f184a6 lumino-2021.12.13/000077500000000000000000000000001415564225700134215ustar00rootroot00000000000000lumino-2021.12.13/.eslintignore000066400000000000000000000003171415564225700161250ustar00rootroot00000000000000examples/ node_modules review **/build **/dist **/lib **/node_modules **/static **/typings **/types coverage *.map.js *.bundle.js *.config.js # jetbrains IDE stuff .idea/ # ms IDE stuff .history/ .vscode/ lumino-2021.12.13/.eslintrc.js000066400000000000000000000036031415564225700156620ustar00rootroot00000000000000module.exports = { env: { browser: true, es6: true, commonjs: true, node: true }, globals: { context: 'readonly', describe: 'readonly', it: 'readonly', before: 'readonly', after: 'readonly', beforeAll: 'readonly', afterAll: 'readonly', beforeEach: 'readonly', afterEach: 'readonly' }, root: true, extends: [ 'eslint:recommended', 'plugin:@typescript-eslint/eslint-recommended', 'plugin:@typescript-eslint/recommended', 'prettier/@typescript-eslint' ], parser: '@typescript-eslint/parser', plugins: ['@typescript-eslint'], rules: { '@typescript-eslint/no-unused-vars': ['warn', { args: 'none' }], '@typescript-eslint/no-use-before-define': 'off', '@typescript-eslint/no-explicit-any': 'off', '@typescript-eslint/no-non-null-assertion': 'off', '@typescript-eslint/no-namespace': 'off', '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/ban-ts-comment': ['warn', { 'ts-ignore': true }], '@typescript-eslint/ban-types': 'warn', '@typescript-eslint/no-non-null-asserted-optional-chain': 'warn', '@typescript-eslint/no-var-requires': 'off', '@typescript-eslint/no-empty-interface': 'off', '@typescript-eslint/triple-slash-reference': 'warn', '@typescript-eslint/no-inferrable-types': 'off', camelcase: 'warn', 'no-inner-declarations': 'off', 'no-prototype-builtins': 'off', 'no-control-regex': 'warn', 'no-undef': 'warn', 'no-case-declarations': 'warn', 'no-useless-escape': 'off', 'prefer-const': 'off', 'sort-imports': [ 'error', { ignoreCase: true, ignoreDeclarationSort: true, ignoreMemberSort: false, memberSyntaxSortOrder: ['none', 'all', 'multiple', 'single'], allowSeparatedGroups: false } ] } }; lumino-2021.12.13/.git-blame-ignore-revs000066400000000000000000000000501415564225700175140ustar00rootroot0000000000000078028483abe79f559bcb789b94e067ce3d64624flumino-2021.12.13/.gitattributes000066400000000000000000000000141415564225700163070ustar00rootroot00000000000000* text=auto lumino-2021.12.13/.github/000077500000000000000000000000001415564225700147615ustar00rootroot00000000000000lumino-2021.12.13/.github/scripts/000077500000000000000000000000001415564225700164505ustar00rootroot00000000000000lumino-2021.12.13/.github/scripts/verdaccio.yml000066400000000000000000000033361415564225700211370ustar00rootroot00000000000000# # This is based on verdaccio's default config file. It allows all users # to do anything, so don't use it on production systems. # # Look here for more config file examples: # https://github.com/verdaccio/verdaccio/tree/master/conf # # path to a directory with all packages storage: ./storage auth: htpasswd: file: ./htpasswd # Maximum amount of users allowed to register, defaults to "+inf". # You can set this to -1 to disable registration. #max_users: 1000 # a list of other known repositories we can talk to uplinks: npmjs: url: https://registry.npmjs.org/ max_fails: 40 maxage: 30m timeout: 60s agent_options: keepAlive: true # Avoid exceeding the max sockets that are allocated per VM. # https://docs.microsoft.com/en-us/azure/app-service/app-service-web-nodejs-best-practices-and-troubleshoot-guide#my-node-application-is-making-excessive-outbound-calls maxSockets: 40 maxFreeSockets: 10 packages: '@*/*': # scoped packages access: $all publish: $all proxy: npmjs '**': # allow all users (including non-authenticated users) to read and # publish all packages # # you can specify usernames/groupnames (depending on your auth plugin) # and three keywords: "$all", "$anonymous", "$authenticated" access: $all # allow all known users to publish packages # (anyone can register by default, remember?) publish: $all # if package is not available locally, proxy requests to 'npmjs' registry proxy: npmjs # log settings logs: - { type: stdout, format: pretty, level: warn } #- {type: file, path: verdaccio.log, level: info} # See https://github.com/verdaccio/verdaccio/issues/301 server: keepAliveTimeout: 0 lumino-2021.12.13/.github/workflows/000077500000000000000000000000001415564225700170165ustar00rootroot00000000000000lumino-2021.12.13/.github/workflows/enforce-label.yml000066400000000000000000000004241415564225700222370ustar00rootroot00000000000000name: Enforce PR label on: pull_request: types: [labeled, unlabeled, opened, edited, synchronize] jobs: enforce-label: runs-on: ubuntu-latest steps: - name: enforce-triage-label uses: jupyterlab/maintainer-tools/.github/actions/enforce-label@v1 lumino-2021.12.13/.github/workflows/tests.yml000066400000000000000000000126501415564225700207070ustar00rootroot00000000000000name: Tests on: [push, pull_request] jobs: test: name: JS strategy: fail-fast: false matrix: os: [ubuntu-latest, windows-latest, macos-latest] browser: [chrome-headless, firefox-headless] exclude: # macos and firefox-headless seems to consistently fail. - os: macos-latest browser: firefox-headless runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v2 - name: Set up Node uses: actions/setup-node@v1 with: node-version: '12.x' # Cache yarn - name: Get yarn cache directory path id: yarn-cache-dir-path run: echo "::set-output name=dir::$(yarn cache dir)" - name: Cache yarn uses: actions/cache@v2 id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`) with: path: ${{ steps.yarn-cache-dir-path.outputs.dir }} key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} restore-keys: | ${{ runner.os }}-yarn- - name: Install dependencies shell: bash run: | set -eux npm install -g yarn yarn - name: Build Source shell: bash run: | set -eux yarn build yarn build:test - name: Run Tests run: | yarn run test:${{ matrix.browser }} build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Set up Node uses: actions/setup-node@v1 with: node-version: '12.x' # Cache yarn - name: Get yarn cache directory path id: yarn-cache-dir-path run: echo "::set-output name=dir::$(yarn cache dir)" - name: Cache yarn uses: actions/cache@v2 id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`) with: path: ${{ steps.yarn-cache-dir-path.outputs.dir }} key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} restore-keys: | ${{ runner.os }}-yarn- - name: Install dependencies shell: bash run: | set -eux npm install -g yarn yarn - name: Check linters shell: bash run: | set -eux yarn lint:check - name: Build Source shell: bash run: | set -eux yarn build yarn build:test - name: Test Examples shell: bash run: | set -eux yarn minimize yarn build:examples yarn test:examples - name: Build Docs run: | set -eux yarn clean yarn docs - name: Publish with Verdaccio run: | set -eux npm install -g verdaccio verdaccio --config .github/scripts/verdaccio.yml & npm set registry http://localhost:4873/ yarn config set registry http://localhost:4873/ git config --global user.email "you@example.com" git config --global user.name "Your Name" yarn run update:versions patch --yes git commit -a -m "Update versions" yarn run publish check_release: runs-on: ubuntu-latest strategy: matrix: group: [check_release, link_check] steps: - name: Checkout uses: actions/checkout@v2 - name: Set up Node uses: actions/setup-node@v1 with: node-version: '14.x' - name: Install Python uses: actions/setup-python@v2 with: python-version: 3.9 architecture: "x64" # Cache pip - name: Get Date id: get-date run: | echo "::set-output name=date::$(/bin/date -u "+%Y%m%d")" shell: bash - name: Get pip cache dir id: pip-cache run: | echo "::set-output name=dir::$(pip cache dir)" - name: Cache pip uses: actions/cache@v2 with: path: ${{ steps.pip-cache.outputs.dir }} key: ${{ runner.os }}-pip-${{ steps.get-date.outputs.date }} restore-keys: | ${{ runner.os }}-pip- ${{ runner.os }}-pip- # Cache yarn - name: Get yarn cache directory path id: yarn-cache-dir-path run: echo "::set-output name=dir::$(yarn cache dir)" - name: Cache yarn uses: actions/cache@v2 id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`) with: path: ${{ steps.yarn-cache-dir-path.outputs.dir }} key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} restore-keys: | ${{ runner.os }}-yarn- - name: Cache checked links if: ${{ matrix.group == 'link_check' }} uses: actions/cache@v2 with: path: ~/.cache/pytest-link-check key: ${{ runner.os }}-linkcheck-${{ hashFiles('**/*.md') }}-md-links restore-keys: | ${{ runner.os }}-linkcheck- - name: Check Release if: ${{ matrix.group == 'check_release' }} uses: jupyter-server/jupyter_releaser/.github/actions/check-release@v1 with: token: ${{ secrets.GITHUB_TOKEN }} - name: Run Link Check if: ${{ matrix.group == 'link_check' }} uses: jupyter-server/jupyter_releaser/.github/actions/check-links@v1 lumino-2021.12.13/.gitignore000066400000000000000000000005371415564225700154160ustar00rootroot00000000000000*.suo *.user .DS_Store dist lib types node_modules build review/api/temp *.tsbuildinfo # copied changelog file docs/source/changelog.md # generated api files docs/source/api # generated example files docs/source/examples # jetbrains ide stuff *.iml .idea/ # ms ide stuff *.code-workspace .history .vscode .jupyter_releaser_checkout .eslintcache lumino-2021.12.13/.prettierignore000066400000000000000000000002731415564225700164660ustar00rootroot00000000000000review **/build **/dist **/node_modules **/lib **/package.json **/static **/types **/.ipynb_checkpoints tests/**/coverage # jetbrains IDE stuff .idea/ # ms IDE stuff .history/ .vscode/ lumino-2021.12.13/.prettierrc000066400000000000000000000001261415564225700156040ustar00rootroot00000000000000{ "singleQuote": true, "trailingComma": "none", "arrowParens": "avoid" }lumino-2021.12.13/.readthedocs.yaml000066400000000000000000000001431415564225700166460ustar00rootroot00000000000000version: 2 sphinx: configuration: docs/source/conf.py conda: environment: docs/environment.yml lumino-2021.12.13/CHANGELOG.md000066400000000000000000001350231415564225700152360ustar00rootroot00000000000000# Changelog ## 2021.12.13 ([Full Changelog](https://github.com/jupyterlab/lumino/compare/v2021.11.4...44b44a408e0849857cc7a7e639b5b0be00ae61ec) - Enforce labels on PRs by @blink1073 in https://github.com/jupyterlab/lumino/pull/267 - Fix transposed display names for arrow keys by @thomasjm in https://github.com/jupyterlab/lumino/pull/268 ## 2021.11.4 ([Full Changelog](https://github.com/jupyterlab/lumino/compare/@lumino/algorithm@1.9.0...6720e2482ac4cd7d7ee7918ecb33cb6a16642d99)) ### Enhancements made - Use composition to improve tab switch [#231](https://github.com/jupyterlab/lumino/pull/231) ([@fcollonval](https://github.com/fcollonval)) ### Bugs fixed - Format keyboard shortcuts according to OS conventions [#258](https://github.com/jupyterlab/lumino/pull/258) ([@jasongrout](https://github.com/jasongrout)) ### Maintenance and upkeep improvements - Run tests on macOS. [#259](https://github.com/jupyterlab/lumino/pull/259) ([@jasongrout](https://github.com/jasongrout)) ### Contributors to this release ([GitHub contributors page for this release](https://github.com/jupyterlab/lumino/graphs/contributors?from=2021-10-25&to=2021-11-04&type=c)) [@fcollonval](https://github.com/search?q=repo%3Ajupyterlab%2Flumino+involves%3Afcollonval+updated%3A2021-10-25..2021-11-04&type=Issues) | [@jasongrout](https://github.com/search?q=repo%3Ajupyterlab%2Flumino+involves%3Ajasongrout+updated%3A2021-10-25..2021-11-04&type=Issues) ## 2021.10.25 ([Full Changelog](https://github.com/jupyterlab/lumino/compare/@lumino/algorithm@1.8.0...1ae240e236e596f8162a58c0289642ab4f392c52)) ### Enhancements made - Add arrow glyph handling to command registry [#252](https://github.com/jupyterlab/lumino/pull/252) ([@PlatinumCD](https://github.com/PlatinumCD)) - Added `PointerEvents` handling to `SplitPanel` [#251](https://github.com/jupyterlab/lumino/pull/251) ([@martaszmit](https://github.com/martaszmit)) - Ignore `keydown` events for modifier keys when accumulating key sequence [#245](https://github.com/jupyterlab/lumino/pull/245) ([@ph-ph](https://github.com/ph-ph)) ### Bugs fixed - Update title appropriately in `AccordionPanel` [#249](https://github.com/jupyterlab/lumino/pull/249) ([@hbcarlos](https://github.com/hbcarlos)) ### Maintenance and upkeep improvements - Add linter check in CI [#242](https://github.com/jupyterlab/lumino/pull/242) ([@fcollonval](https://github.com/fcollonval)) ### Contributors to this release ([GitHub contributors page for this release](https://github.com/jupyterlab/lumino/graphs/contributors?from=2021-09-30&to=2021-10-25&type=c)) [@blink1073](https://github.com/search?q=repo%3Ajupyterlab%2Flumino+involves%3Ablink1073+updated%3A2021-09-30..2021-10-25&type=Issues) | [@fcollonval](https://github.com/search?q=repo%3Ajupyterlab%2Flumino+involves%3Afcollonval+updated%3A2021-09-30..2021-10-25&type=Issues) | [@hbcarlos](https://github.com/search?q=repo%3Ajupyterlab%2Flumino+involves%3Ahbcarlos+updated%3A2021-09-30..2021-10-25&type=Issues) | [@martaszmit](https://github.com/search?q=repo%3Ajupyterlab%2Flumino+involves%3Amartaszmit+updated%3A2021-09-30..2021-10-25&type=Issues) | [@ph-ph](https://github.com/search?q=repo%3Ajupyterlab%2Flumino+involves%3Aph-ph+updated%3A2021-09-30..2021-10-25&type=Issues) | [@PlatinumCD](https://github.com/search?q=repo%3Ajupyterlab%2Flumino+involves%3APlatinumCD+updated%3A2021-09-30..2021-10-25&type=Issues) | [@welcome](https://github.com/search?q=repo%3Ajupyterlab%2Flumino+involves%3Awelcome+updated%3A2021-09-30..2021-10-25&type=Issues) ## 2021.9.30 ([Full Changelog](https://github.com/jupyterlab/lumino/compare/@lumino/algorithm@1.7.0...e6612f622c827b2e85cffb1858fcc3bf1b09be76)) ### Enhancements made - Basic Touch Events [#123](https://github.com/jupyterlab/lumino/pull/123) ([@bign8](https://github.com/bign8)) ### Maintenance and upkeep improvements - Optimise grid rendering [#239](https://github.com/jupyterlab/lumino/pull/239) ([@ibdafna](https://github.com/ibdafna)) ### Documentation improvements - Add static examples in docs [#241](https://github.com/jupyterlab/lumino/pull/241) ([@blink1073](https://github.com/blink1073)) ### Contributors to this release ([GitHub contributors page for this release](https://github.com/jupyterlab/lumino/graphs/contributors?from=2021-09-22&to=2021-09-30&type=c)) [@bign8](https://github.com/search?q=repo%3Ajupyterlab%2Flumino+involves%3Abign8+updated%3A2021-09-22..2021-09-30&type=Issues) | [@blink1073](https://github.com/search?q=repo%3Ajupyterlab%2Flumino+involves%3Ablink1073+updated%3A2021-09-22..2021-09-30&type=Issues) | [@ibdafna](https://github.com/search?q=repo%3Ajupyterlab%2Flumino+involves%3Aibdafna+updated%3A2021-09-22..2021-09-30&type=Issues) | [@jupyterlab-probot](https://github.com/search?q=repo%3Ajupyterlab%2Flumino+involves%3Ajupyterlab-probot+updated%3A2021-09-22..2021-09-30&type=Issues) ## 2021.9.22 ([Full Changelog](https://github.com/jupyterlab/lumino/compare/@lumino/signaling@1.7.2...520444449e74a37a82c34fdf51c70c60059733b3)) ### Bugs fixed - Remove deprecated MediaStreamErrorEvent [#237](https://github.com/jupyterlab/lumino/pull/237) ([@afshin](https://github.com/afshin)) - Class and aria attribute must be changed on title [#232](https://github.com/jupyterlab/lumino/pull/232) ([@fcollonval](https://github.com/fcollonval)) ### Maintenance and upkeep improvements - Add binder link to Readme [#229](https://github.com/jupyterlab/lumino/pull/229) ([@blink1073](https://github.com/blink1073)) - Add binder configuration [#226](https://github.com/jupyterlab/lumino/pull/226) ([@blink1073](https://github.com/blink1073)) ### Documentation improvements - Add screencasts and examples to the README [#234](https://github.com/jupyterlab/lumino/pull/234) ([@jtpio](https://github.com/jtpio)) ### Other merged PRs - Add linter [#230](https://github.com/jupyterlab/lumino/pull/230) ([@fcollonval](https://github.com/fcollonval)) - Bump tar from 4.4.15 to 4.4.19 [#225](https://github.com/jupyterlab/lumino/pull/225) ([@dependabot](https://github.com/dependabot)) ### Contributors to this release ([GitHub contributors page for this release](https://github.com/jupyterlab/lumino/graphs/contributors?from=2021-08-23&to=2021-09-22&type=c)) [@afshin](https://github.com/search?q=repo%3Ajupyterlab%2Flumino+involves%3Aafshin+updated%3A2021-08-23..2021-09-22&type=Issues) | [@blink1073](https://github.com/search?q=repo%3Ajupyterlab%2Flumino+involves%3Ablink1073+updated%3A2021-08-23..2021-09-22&type=Issues) | [@dependabot](https://github.com/search?q=repo%3Ajupyterlab%2Flumino+involves%3Adependabot+updated%3A2021-08-23..2021-09-22&type=Issues) | [@fcollonval](https://github.com/search?q=repo%3Ajupyterlab%2Flumino+involves%3Afcollonval+updated%3A2021-08-23..2021-09-22&type=Issues) | [@jtpio](https://github.com/search?q=repo%3Ajupyterlab%2Flumino+involves%3Ajtpio+updated%3A2021-08-23..2021-09-22&type=Issues) | [@jupyterlab-probot](https://github.com/search?q=repo%3Ajupyterlab%2Flumino+involves%3Ajupyterlab-probot+updated%3A2021-08-23..2021-09-22&type=Issues) ## 2021.8.23 ([Full Changelog](https://github.com/jupyterlab/lumino/compare/@lumino/algorithm@1.6.1...f765c6160f2a4be5ffaccbc590bf7e0d54675ab5)) ### Enhancements made - Add 'padding' argument to fitColumnNames function [#223](https://github.com/jupyterlab/lumino/pull/223) ([@ibdafna](https://github.com/ibdafna)) ### Maintenance and upkeep improvements - Use Check Links Action [#222](https://github.com/jupyterlab/lumino/pull/222) ([@blink1073](https://github.com/blink1073)) ### Documentation improvements - Update release notes [#220](https://github.com/jupyterlab/lumino/pull/220) ([@blink1073](https://github.com/blink1073)) ### Other merged PRs - Add auto-resize function for column widths [#221](https://github.com/jupyterlab/lumino/pull/221) ([@ibdafna](https://github.com/ibdafna)) ### Contributors to this release ([GitHub contributors page for this release](https://github.com/jupyterlab/lumino/graphs/contributors?from=2021-08-12&to=2021-08-23&type=c)) [@blink1073](https://github.com/search?q=repo%3Ajupyterlab%2Flumino+involves%3Ablink1073+updated%3A2021-08-12..2021-08-23&type=Issues) | [@ibdafna](https://github.com/search?q=repo%3Ajupyterlab%2Flumino+involves%3Aibdafna+updated%3A2021-08-12..2021-08-23&type=Issues) ## 2021.8.12 ([Full Changelog](https://github.com/jupyterlab/lumino/compare/@lumino/application@1.23.0...5bd6fe37c0351e48c7e72a9ccb01927a30473020)) ### Enhancements made - New renderer: HyperlinkRenderer [#218](https://github.com/jupyterlab/lumino/pull/218) ([@ibdafna](https://github.com/ibdafna)) ### Maintenance and upkeep improvements - Update dependencies [#217](https://github.com/jupyterlab/lumino/pull/217) ([@afshin](https://github.com/afshin)) - Fix handling of pip cache in CI [#216](https://github.com/jupyterlab/lumino/pull/216) ([@blink1073](https://github.com/blink1073)) ### Other merged PRs - Bump tar from 4.4.13 to 4.4.15 [#215](https://github.com/jupyterlab/lumino/pull/215) ([@dependabot](https://github.com/dependabot)) ### Contributors to this release ([GitHub contributors page for this release](https://github.com/jupyterlab/lumino/graphs/contributors?from=2021-08-03&to=2021-08-12&type=c)) [@afshin](https://github.com/search?q=repo%3Ajupyterlab%2Flumino+involves%3Aafshin+updated%3A2021-08-03..2021-08-12&type=Issues) | [@blink1073](https://github.com/search?q=repo%3Ajupyterlab%2Flumino+involves%3Ablink1073+updated%3A2021-08-03..2021-08-12&type=Issues) | [@dependabot](https://github.com/search?q=repo%3Ajupyterlab%2Flumino+involves%3Adependabot+updated%3A2021-08-03..2021-08-12&type=Issues) | [@ibdafna](https://github.com/search?q=repo%3Ajupyterlab%2Flumino+involves%3Aibdafna+updated%3A2021-08-03..2021-08-12&type=Issues) ## 2021.8.3 ([Full Changelog](https://github.com/jupyterlab/lumino/compare/@lumino/application@1.22.0...d54b9ebc5bc28fa3676c9482da3db5656840934b)) ### Enhancements made - Add accordion panel [#205](https://github.com/jupyterlab/lumino/pull/205) ([@fcollonval](https://github.com/fcollonval)) ### Maintenance and upkeep improvements - Make focus consistent with active element in menus [#187](https://github.com/jupyterlab/lumino/pull/187) ([@marthacryan](https://github.com/marthacryan)) ### Documentation improvements - Remove Errant Changelog Entry [#212](https://github.com/jupyterlab/lumino/pull/212) ([@blink1073](https://github.com/blink1073)) ### Contributors to this release ([GitHub contributors page for this release](https://github.com/jupyterlab/lumino/graphs/contributors?from=2021-07-23&to=2021-08-03&type=c)) [@blink1073](https://github.com/search?q=repo%3Ajupyterlab%2Flumino+involves%3Ablink1073+updated%3A2021-07-23..2021-08-03&type=Issues) | [@fcollonval](https://github.com/search?q=repo%3Ajupyterlab%2Flumino+involves%3Afcollonval+updated%3A2021-07-23..2021-08-03&type=Issues) | [@marthacryan](https://github.com/search?q=repo%3Ajupyterlab%2Flumino+involves%3Amarthacryan+updated%3A2021-07-23..2021-08-03&type=Issues) ## 2021.7.23 ([Full Changelog](https://github.com/jupyterlab/lumino/compare/@lumino/example-datagrid@0.23.0...eee4eb491d5df940bead38b9dff7cba0a94ec482)) ### Enhancements made - Added option to force items' position [#208](https://github.com/jupyterlab/lumino/pull/208) ([@hbcarlos](https://github.com/hbcarlos)) - Add option to not group context menu item by target [#206](https://github.com/jupyterlab/lumino/pull/206) ([@fcollonval](https://github.com/fcollonval)) ### Maintenance and upkeep improvements - Fixes for Check Release [#210](https://github.com/jupyterlab/lumino/pull/210) ([@fcollonval](https://github.com/fcollonval)) ### Documentation improvements ### Contributors to this release ([GitHub contributors page for this release](https://github.com/jupyterlab/lumino/graphs/contributors?from=2021-07-21&to=2021-07-23&type=c)) [@blink1073](https://github.com/search?q=repo%3Ajupyterlab%2Flumino+involves%3Ablink1073+updated%3A2021-07-21..2021-07-23&type=Issues) | [@fcollonval](https://github.com/search?q=repo%3Ajupyterlab%2Flumino+involves%3Afcollonval+updated%3A2021-07-21..2021-07-23&type=Issues) | [@hbcarlos](https://github.com/search?q=repo%3Ajupyterlab%2Flumino+involves%3Ahbcarlos+updated%3A2021-07-21..2021-07-23&type=Issues) | [@welcome](https://github.com/search?q=repo%3Ajupyterlab%2Flumino+involves%3Awelcome+updated%3A2021-07-21..2021-07-23&type=Issues) ## 2017.7.22 ([Full Changelog](https://github.com/jupyterlab/lumino/compare/@lumino/example-datagrid@0.23.0...04b0cf32e49d1371f1c68228850a3941e5b0c6a2)) ### Enhancements made - Added option to force items' position [#208](https://github.com/jupyterlab/lumino/pull/208) ([@hbcarlos](https://github.com/hbcarlos)) - Add option to not group context menu item by target [#206](https://github.com/jupyterlab/lumino/pull/206) ([@fcollonval](https://github.com/fcollonval)) ### Contributors to this release ([GitHub contributors page for this release](https://github.com/jupyterlab/lumino/graphs/contributors?from=2021-07-21&to=2021-07-22&type=c)) [@fcollonval](https://github.com/search?q=repo%3Ajupyterlab%2Flumino+involves%3Afcollonval+updated%3A2021-07-21..2021-07-22&type=Issues) | [@hbcarlos](https://github.com/search?q=repo%3Ajupyterlab%2Flumino+involves%3Ahbcarlos+updated%3A2021-07-21..2021-07-22&type=Issues) | [@welcome](https://github.com/search?q=repo%3Ajupyterlab%2Flumino+involves%3Awelcome+updated%3A2021-07-21..2021-07-22&type=Issues) ## 2021.7.19 ([Full Changelog](https://github.com/jupyterlab/lumino/compare/v2021.6.10...52aee424552e8af82d65fcd8a18b6e80ca5fab77)) ### Documentation improvements ### Other merged PRs - Bump color-string from 1.5.3 to 1.6.0 [#200](https://github.com/jupyterlab/lumino/pull/200) ([@dependabot](https://github.com/dependabot)) - Simplify rendering logic for grids with merged cells [#197](https://github.com/jupyterlab/lumino/pull/197) ([@ibdafna](https://github.com/ibdafna)) ### Contributors to this release ([GitHub contributors page for this release](https://github.com/jupyterlab/lumino/graphs/contributors?from=2021-06-10&to=2021-07-19&type=c)) [@blink1073](https://github.com/search?q=repo%3Ajupyterlab%2Flumino+involves%3Ablink1073+updated%3A2021-06-10..2021-07-19&type=Issues) | [@dependabot](https://github.com/search?q=repo%3Ajupyterlab%2Flumino+involves%3Adependabot+updated%3A2021-06-10..2021-07-19&type=Issues) | [@ibdafna](https://github.com/search?q=repo%3Ajupyterlab%2Flumino+involves%3Aibdafna+updated%3A2021-06-10..2021-07-19&type=Issues) ## 2021.6.10 ([Full Changelog](https://github.com/jupyterlab/lumino/compare/2021.5.20...6c6cf684d121896d971b9f970a095e2a127fe2cb)) ### Bugs fixed - Remove Chrome default scroll on focus [#190](https://github.com/jupyterlab/lumino/pull/190) ([@ibdafna](https://github.com/ibdafna)) ### Maintenance and upkeep improvements - Bump dot-prop from 4.2.0 to 4.2.1 [#194](https://github.com/jupyterlab/lumino/pull/194) ([@dependabot](https://github.com/dependabot)) - Bump lodash from 4.17.19 to 4.17.21 [#193](https://github.com/jupyterlab/lumino/pull/193) ([@dependabot](https://github.com/dependabot)) - Bump handlebars from 4.5.3 to 4.7.7 [#192](https://github.com/jupyterlab/lumino/pull/192) ([@dependabot](https://github.com/dependabot)) - Update Lerna Dependency [#191](https://github.com/jupyterlab/lumino/pull/191) ([@afshin](https://github.com/afshin)) - Use Jupyter releaser [#186](https://github.com/jupyterlab/lumino/pull/186) ([@afshin](https://github.com/afshin)) ### Documentation improvements ### Other merged PRs - Bump browserslist from 4.8.3 to 4.16.6 [#188](https://github.com/jupyterlab/lumino/pull/188) ([@dependabot](https://github.com/dependabot)) ### Contributors to this release ([GitHub contributors page for this release](https://github.com/jupyterlab/lumino/graphs/contributors?from=2021-05-20&to=2021-06-10&type=c)) [@afshin](https://github.com/search?q=repo%3Ajupyterlab%2Flumino+involves%3Aafshin+updated%3A2021-05-20..2021-06-10&type=Issues) | [@blink1073](https://github.com/search?q=repo%3Ajupyterlab%2Flumino+involves%3Ablink1073+updated%3A2021-05-20..2021-06-10&type=Issues) | [@dependabot](https://github.com/search?q=repo%3Ajupyterlab%2Flumino+involves%3Adependabot+updated%3A2021-05-20..2021-06-10&type=Issues) | [@ibdafna](https://github.com/search?q=repo%3Ajupyterlab%2Flumino+involves%3Aibdafna+updated%3A2021-05-20..2021-06-10&type=Issues) ## 2021-5-20 ([Full Changelog](https://github.com/jupyterlab/lumino/compare/2021.5.10...e2b775392b42e98d5c58b9afdc74e92b1631739b)) @lumino/datagrid: 0.24.0 => 0.25.0 ### Enhancements made - Cell merging [#124](https://github.com/jupyterlab/lumino/pull/124) ([@ibdafna](https://github.com/ibdafna)) ### Maintenance and upkeep improvements - Bump codemirror from 5.49.2 to 5.58.2 [#183](https://github.com/jupyterlab/lumino/pull/183) ([@dependabot](https://github.com/dependabot)) - Bump hosted-git-info from 2.8.5 to 2.8.9 [#182](https://github.com/jupyterlab/lumino/pull/182) ([@dependabot](https://github.com/dependabot)) ### Documentation improvements - Fix changelog formatting for 2021-5-10 entry [#185](https://github.com/jupyterlab/lumino/pull/185) ([@blink1073](https://github.com/blink1073)) - Add changelog entry for 2021.5.10 [#184](https://github.com/jupyterlab/lumino/pull/184) ([@blink1073](https://github.com/blink1073)) ### Contributors to this release ([GitHub contributors page for this release](https://github.com/jupyterlab/lumino/graphs/contributors?from=2021-05-10&to=2021-05-20&type=c)) [@blink1073](https://github.com/search?q=repo%3Ajupyterlab%2Flumino+involves%3Ablink1073+updated%3A2021-05-10..2021-05-20&type=Issues) | [@dependabot](https://github.com/search?q=repo%3Ajupyterlab%2Flumino+involves%3Adependabot+updated%3A2021-05-10..2021-05-20&type=Issues) | [@ibdafna](https://github.com/search?q=repo%3Ajupyterlab%2Flumino+involves%3Aibdafna+updated%3A2021-05-10..2021-05-20&type=Issues) ## 2021-5-10 ([Full Changelog](https://github.com/jupyterlab/lumino/compare/2021.4.27...f8466b274fe801bb06931572893fb938808eb2b8)) @lumino/application: 1.19.0 => 1.20.0 @lumino/datagrid: 0.23.0 => 0.24.0 @lumino/default-theme: 0.13.0 => 0.14.0 @lumino/widgets: 1.22.0 => 1.23.0 ### Enhancements made - Implement plus button on TabBar [#108](https://github.com/jupyterlab/lumino/pull/108) ([@nmichaud](https://github.com/nmichaud)) ### Bugs fixed - Fix selection mode [#179](https://github.com/jupyterlab/lumino/pull/179) ([@ibdafna](https://github.com/ibdafna)) ### Maintenance and upkeep improvements - Respect closable attribute on title [#178](https://github.com/jupyterlab/lumino/pull/178) ([@nmichaud](https://github.com/nmichaud)) - Bump underscore from 1.9.1 to 1.13.1 [#181](https://github.com/jupyterlab/lumino/pull/181) ([@dependabot](https://github.com/dependabot)) ### Contributors to this release ([GitHub contributors page for this release](https://github.com/jupyterlab/lumino/graphs/contributors?from=2021-04-27&to=2021-05-10&type=c)) [@afshin](https://github.com/search?q=repo%3Ajupyterlab%2Flumino+involves%3Aafshin+updated%3A2021-04-27..2021-05-10&type=Issues) | [@dependabot](https://github.com/search?q=repo%3Ajupyterlab%2Flumino+involves%3Adependabot+updated%3A2021-04-27..2021-05-10&type=Issues) | [@ellisonbg](https://github.com/search?q=repo%3Ajupyterlab%2Flumino+involves%3Aellisonbg+updated%3A2021-04-27..2021-05-10&type=Issues) | [@ibdafna](https://github.com/search?q=repo%3Ajupyterlab%2Flumino+involves%3Aibdafna+updated%3A2021-04-27..2021-05-10&type=Issues) | [@nmichaud](https://github.com/search?q=repo%3Ajupyterlab%2Flumino+involves%3Anmichaud+updated%3A2021-04-27..2021-05-10&type=Issues) ## 2021-4-27 ([Full Changelog](https://github.com/jupyterlab/lumino/compare/2012.4.19...f4d7e30f37e3bb362b89865a6ce24779c11e91bc)) @lumino/algorithm: 1.5.0 => 1.6.0 @lumino/application: 1.18.0 => 1.19.0 @lumino/collections: 1.5.0 => 1.6.0 @lumino/commands: 1.14.0 => 1.15.0 @lumino/coreutils: 1.7.0 => 1.8.0 @lumino/datagrid: 0.22.0 => 0.23.0 @lumino/datastore: 0.13.0 => 0.14.0 @lumino/default-theme: 0.12.0 => 0.13.0 @lumino/disposable: 1.6.0 => 1.7.0 @lumino/domutils: 1.4.0 => 1.5.0 @lumino/dragdrop: 1.9.0 => 1.10.0 @lumino/keyboard: 1.4.0 => 1.5.0 @lumino/messaging: 1.6.0 => 1.7.0 @lumino/polling: 1.5.0 => 1.6.0 @lumino/properties: 1.4.0 => 1.5.0 @lumino/signaling: 1.6.0 => 1.7.0 @lumino/virtualdom: 1.10.0 => 1.11.0 @lumino/widgets: 1.21.0 => 1.22.0 ### Bugs fixed - Normalize frequency max to respect interval at instantiation time [#177](https://github.com/jupyterlab/lumino/pull/177) ([@afshin](https://github.com/afshin)) ### Documentation improvements - Fix a minor typo in comment [#176](https://github.com/jupyterlab/lumino/pull/176) ([@cnydw](https://github.com/cnydw)) ### Other merged PRs - Switch back to TypeScript 3.6 [#175](https://github.com/jupyterlab/lumino/pull/175) ([@jtpio](https://github.com/jtpio)) - Fix tabindex for menu bar [#174](https://github.com/jupyterlab/lumino/pull/174) ([@marthacryan](https://github.com/marthacryan)) ### Contributors to this release ([GitHub contributors page for this release](https://github.com/jupyterlab/lumino/graphs/contributors?from=2021-04-19&to=2021-04-27&type=c)) [@afshin](https://github.com/search?q=repo%3Ajupyterlab%2Flumino+involves%3Aafshin+updated%3A2021-04-19..2021-04-27&type=Issues) | [@cnydw](https://github.com/search?q=repo%3Ajupyterlab%2Flumino+involves%3Acnydw+updated%3A2021-04-19..2021-04-27&type=Issues) | [@jtpio](https://github.com/search?q=repo%3Ajupyterlab%2Flumino+involves%3Ajtpio+updated%3A2021-04-19..2021-04-27&type=Issues) | [@marthacryan](https://github.com/search?q=repo%3Ajupyterlab%2Flumino+involves%3Amarthacryan+updated%3A2021-04-19..2021-04-27&type=Issues) | [@sccolbert](https://github.com/search?q=repo%3Ajupyterlab%2Flumino+involves%3Asccolbert+updated%3A2021-04-19..2021-04-27&type=Issues) | [@welcome](https://github.com/search?q=repo%3Ajupyterlab%2Flumino+involves%3Awelcome+updated%3A2021-04-19..2021-04-27&type=Issues) ## 2021-4-19 ([Full Changelog](https://github.com/jupyterlab/lumino/compare/2021.4.12...fc0c0ddf950d38e957bfd1e076ffb937a679009f)) @lumino/algorithm: 1.4.0 => 1.5.0 @lumino/application: 1.17.0 => 1.18.0 @lumino/collections: 1.4.0 => 1.5.0 @lumino/commands: 1.13.0 => 1.14.0 @lumino/coreutils: 1.6.0 => 1.7.0 @lumino/datagrid: 0.21.1 => 0.22.0 @lumino/datastore: 0.12.0 => 0.13.0 @lumino/default-theme: 0.11.0 => 0.12.0 @lumino/disposable: 1.5.0 => 1.6.0 @lumino/domutils: 1.3.0 => 1.4.0 @lumino/dragdrop: 1.8.0 => 1.9.0 @lumino/keyboard: 1.3.0 => 1.4.0 @lumino/messaging: 1.5.0 => 1.6.0 @lumino/polling: 1.4.0 => 1.5.0 @lumino/properties: 1.3.0 => 1.4.0 @lumino/signaling: 1.5.0 => 1.6.0 @lumino/virtualdom: 1.9.0 => 1.10.0 @lumino/widgets: 1.20.0 => 1.21.0 ### Merged PRs - Add missing PR to changelog [#171](https://github.com/jupyterlab/lumino/pull/171) ([@blink1073](https://github.com/blink1073)) - Bump ssri from 6.0.1 to 6.0.2 [#173](https://github.com/jupyterlab/lumino/pull/173) ([@dependabot](https://github.com/dependabot)) - Switch to TypeScript 3.9 [#172](https://github.com/jupyterlab/lumino/pull/172) ([@jtpio](https://github.com/jtpio)) - Add exports for sectionlist and celleditorcontroller [#169](https://github.com/jupyterlab/lumino/pull/169) ([@ibdafna](https://github.com/ibdafna)) ### Contributors to this release ([GitHub contributors page for this release](https://github.com/jupyterlab/lumino/graphs/contributors?from=2021-04-12&to=2021-04-19&type=c)) [@blink1073](https://github.com/search?q=repo%3Ajupyterlab%2Flumino+involves%3Ablink1073+updated%3A2021-04-12..2021-04-19&type=Issues) | [@dependabot](https://github.com/search?q=repo%3Ajupyterlab%2Flumino+involves%3Adependabot+updated%3A2021-04-12..2021-04-19&type=Issues) | [@ibdafna](https://github.com/search?q=repo%3Ajupyterlab%2Flumino+involves%3Aibdafna+updated%3A2021-04-12..2021-04-19&type=Issues) | [@jtpio](https://github.com/search?q=repo%3Ajupyterlab%2Flumino+involves%3Ajtpio+updated%3A2021-04-12..2021-04-19&type=Issues) | [@vidartf](https://github.com/search?q=repo%3Ajupyterlab%2Flumino+involves%3Avidartf+updated%3A2021-04-12..2021-04-19&type=Issues) ## 2021-4-12 ([Full Changelog](https://github.com/jupyterlab/lumino/compare/2021.4.9...bcb9734e2f01e625a51de0e58a6c3e5577090d3b)) @lumino/datagrid: 0.21.0 => 0.21.1 ### Merged PRs - Restore getter for `_pressData` [#167](https://github.com/jupyterlab/lumino/pull/167) ([@ibdafna](https://github.com/ibdafna)) ### Contributors to this release ([GitHub contributors page for this release](https://github.com/jupyterlab/lumino/graphs/contributors?from=2021-04-09&to=2021-04-12&type=c)) [@ibdafna](https://github.com/search?q=repo%3Ajupyterlab%2Flumino+involves%3Aibdafna+updated%3A2021-04-09..2021-04-12&type=Issues) ## 2021-4-9 ([Full Changelog](https://github.com/jupyterlab/lumino/compare/2021.3.11...f991ecd1a00df02fa6716e8b0b9f2a2f79a6237e)) @lumino/algorithm: 1.3.3 => 1.4.0 @lumino/application: 1.16.0 => 1.17.0 @lumino/collections: 1.3.3 => 1.4.0 @lumino/commands: 1.12.0 => 1.13.0 @lumino/coreutils: 1.5.3 => 1.6.0 @lumino/datagrid: 0.20.0 => 0.21.0 @lumino/datastore: 0.11.0 => 0.12.0 @lumino/default-theme: 0.10.0 => 0.11.0 @lumino/disposable: 1.4.3 => 1.5.0 @lumino/domutils: 1.2.3 => 1.3.0 @lumino/dragdrop: 1.7.1 => 1.8.0 @lumino/keyboard: 1.2.3 => 1.3.0 @lumino/messaging: 1.4.3 => 1.5.0 @lumino/polling: 1.3.3 => 1.4.0 @lumino/properties: 1.2.3 => 1.3.0 @lumino/signaling: 1.4.3 => 1.5.0 @lumino/virtualdom: 1.8.0 => 1.9.0 @lumino/widgets: 1.19.0 => 1.20.0 ### Merged PRs - Clean Up CI [#166](https://github.com/jupyterlab/lumino/pull/166) ([@jtpio](https://github.com/jtpio)) - Bump y18n from 4.0.0 to 4.0.1 [#164](https://github.com/jupyterlab/lumino/pull/164) ([@dependabot](https://github.com/dependabot)) - Ctrl-click to toggle single row or column selections [#163](https://github.com/jupyterlab/lumino/pull/163) ([@ibdafna](https://github.com/ibdafna)) - Update documentation badge [#162](https://github.com/jupyterlab/lumino/pull/162) ([@blink1073](https://github.com/blink1073)) - Change one BasicMouseHandler properties from private to protected [#161](https://github.com/jupyterlab/lumino/pull/161) ([@ibdafna](https://github.com/ibdafna)) - Update RTD Link to Point to Stable Version [#159](https://github.com/jupyterlab/lumino/pull/159) ([@blink1073](https://github.com/blink1073)) - Update Badges in Readme [#158](https://github.com/jupyterlab/lumino/pull/158) ([@blink1073](https://github.com/blink1073)) - Fix docs target for polling [#155](https://github.com/jupyterlab/lumino/pull/155) ([@bollwyvl](https://github.com/bollwyvl)) ### Contributors to this release ([GitHub contributors page for this release](https://github.com/jupyterlab/lumino/graphs/contributors?from=2021-03-23&to=2021-04-09&type=c)) [@blink1073](https://github.com/search?q=repo%3Ajupyterlab%2Flumino+involves%3Ablink1073+updated%3A2021-03-23..2021-04-09&type=Issues) | [@bollwyvl](https://github.com/search?q=repo%3Ajupyterlab%2Flumino+involves%3Abollwyvl+updated%3A2021-03-23..2021-04-09&type=Issues) | [@dependabot](https://github.com/search?q=repo%3Ajupyterlab%2Flumino+involves%3Adependabot+updated%3A2021-03-23..2021-04-09&type=Issues) | [@ibdafna](https://github.com/search?q=repo%3Ajupyterlab%2Flumino+involves%3Aibdafna+updated%3A2021-03-23..2021-04-09&type=Issues) | [@jtpio](https://github.com/search?q=repo%3Ajupyterlab%2Flumino+involves%3Ajtpio+updated%3A2021-03-23..2021-04-09&type=Issues) | [@welcome](https://github.com/search?q=repo%3Ajupyterlab%2Flumino+involves%3Awelcome+updated%3A2021-03-23..2021-04-09&type=Issues) ## 2021-3-11 @lumino/application: 1.15.0 => 1.16.0 @lumino/datagrid: 0.19.0 => 0.20.0 @lumino/default-theme: 0.9.0 => 0.10.0 @lumino/widgets: 1.18.0 => 1.19.0 - Add Sphinx Documentation [#157](https://github.com/jupyterlab/lumino/pull/157) ([@afshin](https://github.com/afshin)) - Bump elliptic from 6.5.3 to 6.5.4 [#156](https://github.com/jupyterlab/lumino/pull/156) ([@dependabot](https://github.com/dependabot)) - Add ARIA roles to tabs - lumino update [#132](https://github.com/jupyterlab/lumino/pull/132) ([@telamonian](https://github.com/telamonian)) ## 2021-1-19 @lumino/widgets@1.18.0 @lumino/example-dockpanel@0.7.0 @lumino/example-datastore@0.7.0 @lumino/example-datagrid@0.16.0 @lumino/default-theme@0.9.0 @lumino/datagrid@0.19.0 @lumino/application@1.15.0 - Allow passing of `tag` into `widget` constructor [#150](https://github.com/jupyterlab/lumino/pull/150) ([@telamonian](https://github.com/telamonian)) - Add checkbox aria role to toggleable commands [#149](https://github.com/jupyterlab/lumino/pull/149) ([@marthacryan](https://github.com/marthacryan)) - Remove leftover SectionResizeRequest [#148](https://github.com/jupyterlab/lumino/pull/148) ([@martinRenou](https://github.com/martinRenou)) ## 2021-1-5 @lumino/example-datagrid@0.15.0 @lumino/datagrid@0.18.0 - DataGrid mouse handler: Expose pressData for subclasses [#146](https://github.com/jupyterlab/lumino/pull/146) ([@martinRenou](https://github.com/martinRenou)) - Make \_repaintRegion a protected method [#145](https://github.com/jupyterlab/lumino/pull/145) ([@martinRenou](https://github.com/martinRenou)) - Bump ini from 1.3.5 to 1.3.7 [#143](https://github.com/jupyterlab/lumino/pull/143) ([@dependabot](https://github.com/dependabot)) ## 2020-12-11 @lumino/widgets@1.17.0 @lumino/example-dockpanel@0.6.0 @lumino/example-datastore@0.6.0 @lumino/example-datagrid@0.14.0 @lumino/default-theme@0.8.0 @lumino/datagrid@0.17.0 @lumino/application@1.14.0 - Switch to GitHub Actions [#142](https://github.com/jupyterlab/lumino/pull/142) ([@afshin](https://github.com/afshin)) - Add text wrapping [#140](https://github.com/jupyterlab/lumino/pull/140) ([@ibdafna](https://github.com/ibdafna)) - Constrain tabs to their source DockPanel (opt-in) [#137](https://github.com/jupyterlab/lumino/pull/137) ([@piersdeseilligny](https://github.com/piersdeseilligny)) ## 2020-12-3 @lumino/widgets@1.16.1 @lumino/example-dockpanel@0.5.1 @lumino/example-datastore@0.5.1 @lumino/example-datagrid@0.13.1 @lumino/dragdrop@1.7.1 @lumino/default-theme@0.7.1 @lumino/datagrid@0.16.1 @lumino/application@1.13.1 - Specify the CSS javascript module imports explicitly in package.json. [#139](https://github.com/jupyterlab/lumino/pull/139) ([@jasongrout](https://github.com/jasongrout)) ## 2020-12-1 @lumino/widgets@1.16.0 @lumino/example-dockpanel@0.5.0 @lumino/example-datastore@0.5.0 @lumino/example-datagrid@0.13.0 @lumino/dragdrop@1.7.0 @lumino/default-theme@0.7.0 @lumino/datagrid@0.16.0 @lumino/application@1.13.0 - Add style index.js files to optionally consume the CSS via a js module import [#136](https://github.com/jupyterlab/lumino/pull/136) ([@jasongrout](https://github.com/jasongrout)) ## 2020-11-30 @lumino/widgets@1.15.0 @lumino/virtualdom@1.8.0 @lumino/example-dockpanel@0.4.0 @lumino/example-datastore@0.4.0 @lumino/example-datagrid@0.12.0 @lumino/default-theme@0.6.0 @lumino/datagrid@0.15.0 @lumino/commands@1.12.0 @lumino/application@1.12.0 - Bump highlight.js from 9.17.1 to 9.18.5 [#135](https://github.com/jupyterlab/lumino/pull/135) ([@dependabot](https://github.com/dependabot)) - Batch Add Items to Command Pallette [#133](https://github.com/jupyterlab/lumino/pull/133) ([@jhamet93](https://github.com/jhamet93)) - Add aria roles to menus [#131](https://github.com/jupyterlab/lumino/pull/131) ([@marthacryan](https://github.com/marthacryan)) - Add isToggleable command state [#129](https://github.com/jupyterlab/lumino/pull/129) ([@marthacryan](https://github.com/marthacryan)) ## 2020-11-2 @lumino/widgets@1.14.1 @lumino/example-dockpanel@0.3.6 @lumino/example-datastore@0.3.6 @lumino/example-datagrid@0.11.1 @lumino/default-theme@0.5.1 @lumino/datagrid@0.14.1 @lumino/commands@1.11.4 @lumino/application@1.11.1 - Fix sluggish tab dragging in the tab bar. [#128](https://github.com/jupyterlab/lumino/pull/128) ([@subhav](https://github.com/subhav)) - Improve note about performance of commandExecuted handlers. [#127](https://github.com/jupyterlab/lumino/pull/127) ([@ellisonbg](https://github.com/ellisonbg)) - Bump node-fetch from 2.6.0 to 2.6.1 [#121](https://github.com/jupyterlab/lumino/pull/121) ([@dependabot](https://github.com/dependabot)) - Bump http-proxy from 1.18.0 to 1.18.1 [#120](https://github.com/jupyterlab/lumino/pull/120) ([@dependabot](https://github.com/dependabot)) ## 2020-8-24 @lumino/example-datagrid@0.11.0 @lumino/datagrid@0.14.0 - Make private \_drawCornerHeaderRegion protected drawCornerHeaderRegion [#116](https://github.com/jupyterlab/lumino/pull/116) ([@lmcnichols](https://github.com/lmcnichols)) - Text eliding with ellipsis on datagrid text renderer [#105](https://github.com/jupyterlab/lumino/pull/105) ([@nmichaud](https://github.com/nmichaud)) ## 2020-8-20 @lumino/widgets@1.14.0 @lumino/example-dockpanel@0.3.5 @lumino/example-datastore@0.3.5 @lumino/example-datagrid@0.10.0 @lumino/default-theme@0.5.0 @lumino/datastore@0.11.0 @lumino/datagrid@0.13.0 @lumino/application@1.11.0 - mouseDown now uses cell, column, and row selection modes [#114](https://github.com/jupyterlab/lumino/pull/114) ([@kgoo124](https://github.com/kgoo124)) - Double-click to edit tab title in TabBars [#112](https://github.com/jupyterlab/lumino/pull/112) ([@nmichaud](https://github.com/nmichaud)) - Give extending classes access to some of the data grid's paint utilities. [#111](https://github.com/jupyterlab/lumino/pull/111) ([@lmcnichols](https://github.com/lmcnichols)) - Fix for DockPanel.tabsMovable to set false to all tabs [#109](https://github.com/jupyterlab/lumino/pull/109) ([@nmichaud](https://github.com/nmichaud)) - Modified function spliceArray in datastore/src/listfield.ts so that it behaves like Array.splice on large inputs. [#101](https://github.com/jupyterlab/lumino/pull/101) ([@lmcnichols](https://github.com/lmcnichols)) - Bump elliptic from 6.5.2 to 6.5.3 [#99](https://github.com/jupyterlab/lumino/pull/99) ([@dependabot](https://github.com/dependabot)) ## 2020-7-27 @lumino/widgets@1.13.4 @lumino/example-dockpanel@0.3.4 @lumino/example-datastore@0.3.4 @lumino/example-datagrid@0.9.0 @lumino/dragdrop@1.6.4 @lumino/default-theme@0.4.4 @lumino/datagrid@0.12.0 @lumino/application@1.10.4 - Change the Drag class's private method \_moveDragImage to a public method moveDragImage. [#96](https://github.com/jupyterlab/lumino/pull/96) ([@lmcnichols](https://github.com/lmcnichols)) ## 2020-7-21 @lumino/widgets@1.13.3 @lumino/virtualdom@1.7.3 @lumino/signaling@1.4.3 @lumino/properties@1.2.3 @lumino/polling@1.3.3 @lumino/messaging@1.4.3 @lumino/keyboard@1.2.3 @lumino/example-dockpanel@0.3.3 @lumino/example-datastore@0.3.3 @lumino/example-datagrid@0.8.1 @lumino/dragdrop@1.6.3 @lumino/domutils@1.2.3 @lumino/disposable@1.4.3 @lumino/default-theme@0.4.3 @lumino/datastore@0.10.3 @lumino/datagrid@0.11.1 @lumino/coreutils@1.5.3 @lumino/commands@1.11.3 @lumino/collections@1.3.3 @lumino/application@1.10.3 @lumino/algorithm@1.3.3 - Have the DataGrid syncViewport when receiving a DataModel.ChangedArgs signal of type "rows-moved" or "columns-moved" [#94](https://github.com/jupyterlab/lumino/pull/94) ([@lmcnichols](https://github.com/lmcnichols)) ## 2020-7-21 @lumino/example-datagrid@0.8.0 @lumino/datagrid@0.11.0 - Make cursorForHandle and it's argument type accessible from outside BasicMouseHandler. [#92](https://github.com/jupyterlab/lumino/pull/92) ([@lmcnichols](https://github.com/lmcnichols)) - Bump lodash from 4.17.15 to 4.17.19 [#90](https://github.com/jupyterlab/lumino/pull/90) ([@dependabot](https://github.com/dependabot)) ## 2020-7-5 @lumino/example-datagrid@0.7.0 @lumino/datagrid@0.10.0 - CellEditors now render in front of the DataGrid [#87](https://github.com/jupyterlab/lumino/pull/87) ([@kgoo124](https://github.com/kgoo124)) ## 2020-6-26 @lumino/widgets@1.13.2 @lumino/virtualdom@1.7.2 @lumino/signaling@1.4.2 @lumino/properties@1.2.2 @lumino/polling@1.3.2 @lumino/messaging@1.4.2 @lumino/keyboard@1.2.2 @lumino/example-dockpanel@0.3.2 @lumino/example-datastore@0.3.2 @lumino/example-datagrid@0.6.1 @lumino/dragdrop@1.6.2 @lumino/domutils@1.2.2 @lumino/disposable@1.4.2 @lumino/default-theme@0.4.2 @lumino/datastore@0.10.2 @lumino/datagrid@0.9.1 @lumino/coreutils@1.5.2 @lumino/commands@1.11.2 @lumino/collections@1.3.2 @lumino/application@1.10.2 @lumino/algorithm@1.3.2 - Revert "chore(build): Bump Typescript to 3.9.2" [#84](https://github.com/jupyterlab/lumino/pull/84) ([@telamonian](https://github.com/telamonian)) ## 2020-6-24 @lumino/widgets@1.13.1 @lumino/virtualdom@1.7.1 @lumino/signaling@1.4.1 @lumino/properties@1.2.1 @lumino/polling@1.3.1 @lumino/messaging@1.4.1 @lumino/keyboard@1.2.1 @lumino/example-dockpanel@0.3.1 @lumino/example-datastore@0.3.1 @lumino/example-datagrid@0.6.0 @lumino/dragdrop@1.6.1 @lumino/domutils@1.2.1 @lumino/disposable@1.4.1 @lumino/default-theme@0.4.1 @lumino/datastore@0.10.1 @lumino/datagrid@0.9.0 @lumino/coreutils@1.5.1 @lumino/commands@1.11.1 @lumino/collections@1.3.1 @lumino/application@1.10.1 @lumino/algorithm@1.3.1 - fix columnCount signature [#82](https://github.com/jupyterlab/lumino/pull/82) ([@mbektas](https://github.com/mbektas)) - unsubscribe from grid wheel events on editor dispose [#80](https://github.com/jupyterlab/lumino/pull/80) ([@mbektas](https://github.com/mbektas)) - chore(build): Bump Typescript to 3.9.2 [#75](https://github.com/jupyterlab/lumino/pull/75) ([@GordonSmith](https://github.com/GordonSmith)) ## 2020-5-23 @lumino/widgets@1.13.0-alpha.0 @lumino/virtualdom@1.7.0-alpha.0 @lumino/signaling@1.4.0-alpha.0 @lumino/properties@1.2.0-alpha.0 @lumino/polling@1.3.0-alpha.0 @lumino/messaging@1.4.0-alpha.0 @lumino/keyboard@1.2.0-alpha.0 @lumino/example-dockpanel@0.3.0-alpha.0 @lumino/example-dockpanel-iife@0.1.0-alpha.0 @lumino/example-dockpanel-amd@0.1.0-alpha.0 @lumino/example-datastore@0.3.0-alpha.0 @lumino/example-datagrid@0.5.0-alpha.0 @lumino/dragdrop@1.6.0-alpha.0 @lumino/domutils@1.2.0-alpha.0 @lumino/disposable@1.4.0-alpha.0 @lumino/default-theme@0.4.0-alpha.0 @lumino/datastore@0.10.0-alpha.0 @lumino/datagrid@0.8.0-alpha.0 @lumino/coreutils@1.5.0-alpha.0 @lumino/commands@1.11.0-alpha.0 @lumino/collections@1.3.0-alpha.0 @lumino/application@1.10.0-alpha.0 @lumino/algorithm@1.3.0-alpha.0 - Added type search to command pallet search input [#57](https://github.com/jupyterlab/lumino/pull/57) ([@ggbhat](https://github.com/ggbhat)) - feat(build): Add UMD support [#40](https://github.com/jupyterlab/lumino/pull/40) ([@GordonSmith](https://github.com/GordonSmith)) ## 2020-5-12 @lumino/widgets@1.12.2 @lumino/signaling@1.3.6 @lumino/polling@1.2.2 @lumino/example-dockpanel@0.2.2 @lumino/example-datastore@0.2.13 @lumino/example-datagrid@0.4.2 @lumino/dragdrop@1.5.3 @lumino/disposable@1.3.6 @lumino/default-theme@0.3.2 @lumino/datastore@0.9.2 @lumino/datagrid@0.7.2 @lumino/commands@1.10.3 @lumino/application@1.9.2 - Fix `disconnectAll` implementation. [#71](https://github.com/jupyterlab/lumino/pull/71) ([@AlbertHilb](https://github.com/AlbertHilb)) ## 2020-5-7 @lumino/widgets@1.12.1 @lumino/polling@1.2.1 @lumino/example-dockpanel@0.2.1 @lumino/example-datastore@0.2.12 @lumino/example-datagrid@0.4.1 @lumino/dragdrop@1.5.2 @lumino/default-theme@0.3.1 @lumino/datastore@0.9.1 @lumino/datagrid@0.7.1 @lumino/coreutils@1.4.3 @lumino/commands@1.10.2 @lumino/application@1.9.1 - Tell bundlers to not package a crypto module for the browser. [#70](https://github.com/jupyterlab/lumino/pull/70) ([@jasongrout](https://github.com/jasongrout)) - Fix boolean logic when false is specified [#69](https://github.com/jupyterlab/lumino/pull/69) ([@nmichaud](https://github.com/nmichaud)) - Bump jquery from 3.4.1 to 3.5.0 [#68](https://github.com/jupyterlab/lumino/pull/68) ([@dependabot](https://github.com/dependabot)) - Fix namespacing for 'invalid' classname [#67](https://github.com/jupyterlab/lumino/pull/67) ([@nmichaud](https://github.com/nmichaud)) ## 2020-4-24 @lumino/widgets@1.12.0 @lumino/polling@1.2.0 @lumino/example-dockpanel@0.2.0 @lumino/example-datastore@0.2.11 @lumino/example-datagrid@0.4.0 @lumino/default-theme@0.3.0 @lumino/datagrid@0.7.0 @lumino/application@1.9.0 - Fixes tabsMovable on DockPanel [#66](https://github.com/jupyterlab/lumino/pull/66) ([@nmichaud](https://github.com/nmichaud)) - Customize minimum row and column section sizes for datagrid [#65](https://github.com/jupyterlab/lumino/pull/65) ([@nmichaud](https://github.com/nmichaud)) ## 2020-3-22 @lumino/polling@1.1.0 @lumino/example-datastore@0.2.10 @lumino/example-datagrid@0.3.4 @lumino/datastore@0.9.0 @lumino/datagrid@0.6.0 ## 2020-2-19 @lumino/widgets@1.11.1 @lumino/virtualdom@1.6.1 @lumino/signaling@1.3.5 @lumino/polling@1.0.4 @lumino/example-dockpanel@0.1.31 @lumino/example-datastore@0.2.9 @lumino/example-datagrid@0.3.3 @lumino/dragdrop@1.5.1 @lumino/disposable@1.3.5 @lumino/default-theme@0.2.4 @lumino/datastore@0.8.4 @lumino/datagrid@0.5.3 @lumino/commands@1.10.1 @lumino/application@1.8.4 - Yet another fix for vdom nodes with custom renderers [#53](https://github.com/jupyterlab/lumino/pull/53) ([@telamonian](https://github.com/telamonian)) - Fix names for poll tests. [#50](https://github.com/jupyterlab/lumino/pull/50) ([@afshin](https://github.com/afshin)) - Fix broken links in polling package and signaling tests. [#49](https://github.com/jupyterlab/lumino/pull/49) ([@afshin](https://github.com/afshin)) ## 2020-2-10 @lumino/widgets@1.11.0 @lumino/virtualdom@1.6.0 @lumino/example-dockpanel@0.1.30 @lumino/example-datastore@0.2.8 @lumino/example-datagrid@0.3.2 @lumino/default-theme@0.2.3 @lumino/datagrid@0.5.2 @lumino/commands@1.10.0 @lumino/application@1.8.3 - IRenderer cleanup; normalize icon fields across all interfaces [#46](https://github.com/jupyterlab/lumino/pull/46) ([@telamonian](https://github.com/telamonian)) ## 2020-1-27 @lumino/widgets@1.10.2 @lumino/virtualdom@1.5.0 @lumino/example-dockpanel@0.1.29 @lumino/example-datastore@0.2.7 @lumino/example-datagrid@0.3.1 @lumino/default-theme@0.2.2 @lumino/datagrid@0.5.1 @lumino/application@1.8.2 - Simplified/improved custom rendering of virtual nodes: removed `hpass` and `VirtualElementPass`, added optional `renderer` param [#44](https://github.com/jupyterlab/lumino/pull/44) ([@telamonian](https://github.com/telamonian)) ## 2020-1-24 @lumino/widgets@1.10.1 @lumino/virtualdom@1.4.1 @lumino/example-dockpanel@0.1.28 @lumino/example-datastore@0.2.6 @lumino/example-datagrid@0.3.0 @lumino/default-theme@0.2.1 @lumino/datagrid@0.5.0 @lumino/application@1.8.1 - Remove 'sourceMap' from tsconfig in `@lumino/virtualdom` [#41](https://github.com/jupyterlab/lumino/pull/41) ([@zemeolotu](https://github.com/zemeolotu)) - Start a change log [#38](https://github.com/jupyterlab/lumino/pull/38) ([@blink1073](https://github.com/blink1073)) - DataGrid Cell Editing [#14](https://github.com/jupyterlab/lumino/pull/14) ([@mbektas](https://github.com/mbektas)) ## 2020-1-8 @lumino/widgets@1.10.0 @lumino/example-dockpanel@0.1.27 @lumino/example-datastore@0.2.5 @lumino/example-datagrid@0.2.6 @lumino/dragdrop@1.5.0 @lumino/default-theme@0.2.0 @lumino/datagrid@0.4.0 @lumino/commands@1.9.2 @lumino/application@1.8.0 - Update selector, data attribute, and event namespaces. [#20](https://github.com/jupyterlab/lumino/pull/20) ([@afshin](https://github.com/afshin)) ## 2020-1-2 @lumino/widgets@1.9.7 @lumino/virtualdom@1.4.0 @lumino/signaling@1.3.4 @lumino/properties@1.1.6 @lumino/polling@1.0.3 @lumino/messaging@1.3.3 @lumino/keyboard@1.1.6 @lumino/example-dockpanel@0.1.26 @lumino/example-datastore@0.2.4 @lumino/example-datagrid@0.2.5 @lumino/dragdrop@1.4.4 @lumino/domutils@1.1.7 @lumino/disposable@1.3.4 @lumino/default-theme@0.1.12 @lumino/datastore@0.8.3 @lumino/datagrid@0.3.5 @lumino/coreutils@1.4.2 @lumino/commands@1.9.1 @lumino/collections@1.2.3 @lumino/application@1.7.7 @lumino/algorithm@1.2.3 - Improve handling of attributes for hpass virtualdom elements [#36](https://github.com/jupyterlab/lumino/pull/36) ([@telamonian](https://github.com/telamonian)) - Fix `output.path` for webpack 4 [#35](https://github.com/jupyterlab/lumino/pull/35) ([@telamonian](https://github.com/telamonian)) ## 2019-12-19 @lumino/widgets@1.9.6 @lumino/example-dockpanel@0.1.25 @lumino/example-datastore@0.2.3 @lumino/example-datagrid@0.2.4 @lumino/default-theme@0.1.11 @lumino/datagrid@0.3.4 @lumino/commands@1.9.0 @lumino/application@1.7.6 - Allow commands to accept partial json objects [#32](https://github.com/jupyterlab/lumino/pull/32) ([@blink1073](https://github.com/blink1073)) ## 2019-12-17 @lumino/widgets@1.9.5 @lumino/virtualdom@1.3.0 @lumino/signaling@1.3.3 @lumino/properties@1.1.5 @lumino/polling@1.0.2 @lumino/messaging@1.3.2 @lumino/keyboard@1.1.5 @lumino/example-dockpanel@0.1.24 @lumino/example-datastore@0.2.2 @lumino/example-datagrid@0.2.3 @lumino/dragdrop@1.4.3 @lumino/domutils@1.1.6 @lumino/disposable@1.3.3 @lumino/default-theme@0.1.10 @lumino/datastore@0.8.2 @lumino/datagrid@0.3.3 @lumino/coreutils@1.4.1 @lumino/commands@1.8.1 @lumino/collections@1.2.2 @lumino/application@1.7.5 @lumino/algorithm@1.2.2 - Update dependencies [#31](https://github.com/jupyterlab/lumino/pull/31) ([@blink1073](https://github.com/blink1073)) - Use the standby value generated instead of ignoring it. [#30](https://github.com/jupyterlab/lumino/pull/30) ([@jasongrout](https://github.com/jasongrout)) - Adds a "pass thru" virtual element [#29](https://github.com/jupyterlab/lumino/pull/29) ([@telamonian](https://github.com/telamonian)) - Update API reports [#28](https://github.com/jupyterlab/lumino/pull/28) ([@vidartf](https://github.com/vidartf)) - chore(build): Bump typescript to version 3.6.4 [#27](https://github.com/jupyterlab/lumino/pull/27) ([@GordonSmith](https://github.com/GordonSmith)) - chore(build): Add missing package.json dependencies [#24](https://github.com/jupyterlab/lumino/pull/24) ([@GordonSmith](https://github.com/GordonSmith)) - Enable / disable runtime tab dragging in DockPanel [#23](https://github.com/jupyterlab/lumino/pull/23) ([@GordonSmith](https://github.com/GordonSmith)) lumino-2021.12.13/CONTRIBUTING.md000066400000000000000000000031631415564225700156550ustar00rootroot00000000000000# Contributing to Lumino Lumino is a subproject of Project Jupyter and subject to the [Jupyter governance](https://github.com/jupyter/governance) and [Code of conduct](https://github.com/jupyter/governance/blob/master/conduct/code_of_conduct.md). ## General Guidelines For general documentation about contributing to Jupyter projects, see the [Project Jupyter Contributor Documentation](https://jupyter.readthedocs.io/en/latest/contributing/content-contributor.html). ## Setting up a development environment Lumino requires [nodejs](https://nodejs.org/en/) and [yarn](https://yarnpkg.com/lang/en/) for local development. After cloning Lumino, run the following to install dependencies and build the source: ```bash yarn yarn build:src ``` ## Tests The tests are written using karma to simulate a browser environment. To run the tests, run: ```bash yarn build:test yarn test # optionally test:chrome, test:firefox, or test:ie ``` ## Examples Lumino examples are in the `examples/` folder. To build and run an example: ```bash yarn build:src yarn minimize yarn build:examples cd example/dockpanel ``` Open the `index.html` file in a browser to see the running example. There are also tests in some of the examples. These can be run as: ```bash yarn test:examples ``` ## Static Examples There are static examples built into the documentation. Having them in docs allows us to test examples in the ReadTheDocs build for a PR. To add an example to the static examples: - Add appropriate link in: `docs/source/examples.rst` - Add the example name to the `EXAMPLES` in `docs/source/conf.py` - Add `ignore-links` config in `package.json` lumino-2021.12.13/LICENSE000066400000000000000000000057151415564225700144360ustar00rootroot00000000000000Copyright (c) 2019 Project Jupyter Contributors All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Copyright (c) 2014-2017, PhosphorJS Contributors All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. lumino-2021.12.13/README.md000066400000000000000000000050761415564225700147100ustar00rootroot00000000000000# Lumino [![Build Status](https://github.com/jupyterlab/lumino/workflows/Tests/badge.svg?branch=master)](https://github.com/jupyterlab/lumino/actions?query=branch%3Amaster+workflow%3A%22Tests%22) [![Documentation Status](https://readthedocs.org/projects/jupyterlab/badge/?version=stable)](http://lumino.readthedocs.io/en/stable/) [![GitHub](https://img.shields.io/badge/issue_tracking-github-blue.svg)](https://github.com/jupyterlab/lumino/issues) [![Discourse](https://img.shields.io/badge/help_forum-discourse-blue.svg)](https://discourse.jupyter.org/c/jupyterlab) [![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/jupyterlab/lumino/master?urlpath=lab/tree/examples) Lumino is a set of JavaScript packages, written in TypeScript, that provide a rich toolkit of widgets, layouts, events, and data structures. These enable developers to construct extensible high-performance desktop-like web applications, such as JupyterLab. Lumino was formerly known as PhosphorJS. Lumino is Jupyter project and follows the Jupyter [Community Guides and Code of Conduct](https://jupyter.readthedocs.io/en/latest/community/content-community.html). ## Examples ### JupyterLab [JupyterLab](https://github.com/jupyterlab/jupyterlab) is an extensible environment for interactive and reproducible computing. You can try it live in a web browser (without installing anything) by clicking on the link below: [![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/jupyterlab/lumino/master?urlpath=lab/tree/examples) ![jupyterlab](https://user-images.githubusercontent.com/591645/133745885-8905be8e-afc0-466d-afda-21ed3cf0d813.png) ### Examples in the repository This repository contains several examples making use of Lumino Widgets such as the `DockPanel` and the `DataGrid`. The example can be interacted with live in the browser by following this link: [![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/jupyterlab/lumino/master?urlpath=lab/tree/examples) ![examples-binder](https://user-images.githubusercontent.com/591645/133746521-ca0debce-f453-417b-bca3-60d10044857f.gif) ### External Examples - [Using Lumino in a Vue.js application](https://github.com/kinow/vue-lumino) - [wasmboy: Game Boy / Game Boy Color Emulator Library, written for WebAssembly using AssemblyScript](https://github.com/torch2424/wasmboy) ## Usage To learn more on how to use Lumino, check out the documentation: https://lumino.readthedocs.io/en/latest/ ## Development See [CONTRIBUTING.md](./CONTRIBUTING.md) to know how to contribute and set up a development environment. lumino-2021.12.13/RELEASE.md000066400000000000000000000020721415564225700150240ustar00rootroot00000000000000# Release instructions for Lumino ## Using `jupyter_releaser` The recommended way to make a release is to use [`jupyter_releaser`](https://github.com/jupyter-server/jupyter_releaser#checklist-for-adoption). Because `lumino` uses independent versions, the versioning must be done manually as follows: ```bash yarn yarn run update:versions git commit -a -m "Update versions" git push origin main ``` If you forget to bump the versions and need to undo: ```bash git revert git push origin main push --delete origin ``` ## Manual Release To create a manual release, perform the following steps: Check for releases since the last published version to determine appropriate patch/minor/major version changes. If a dependent package moves by minor/major, then that package needs to jump minor/major as well. ```bash git clean -dfx yarn yarn run update:versions # Update the changelog with changed packages (minor or higher) and included PRs. # Tag the release with the date, e.g. 2021.4.9 # yarn run publish # Push any changes to main ``` lumino-2021.12.13/api-extractor-base.json000066400000000000000000000337121415564225700200140ustar00rootroot00000000000000/** * Config file for API Extractor. For more info, please visit: https://api-extractor.com */ { "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", /** * Optionally specifies another JSON config file that this file extends from. This provides a way for * standard settings to be shared across multiple projects. * * If the path starts with "./" or "../", the path is resolved relative to the folder of the file that contains * the "extends" field. Otherwise, the first path segment is interpreted as an NPM package name, and will be * resolved using NodeJS require(). * * SUPPORTED TOKENS: none * DEFAULT VALUE: "" */ // "extends": "./shared/api-extractor-base.json" // "extends": "my-package/include/api-extractor-base.json" /** * Determines the "" token that can be used with other config file settings. The project folder * typically contains the tsconfig.json and package.json config files, but the path is user-defined. * * The path is resolved relative to the folder of the config file that contains the setting. * * The default value for "projectFolder" is the token "", which means the folder is determined by traversing * parent folders, starting from the folder containing api-extractor.json, and stopping at the first folder * that contains a tsconfig.json file. If a tsconfig.json file cannot be found in this way, then an error * will be reported. * * SUPPORTED TOKENS: * DEFAULT VALUE: "" */ // "projectFolder": "..", /** * (REQUIRED) Specifies the .d.ts file to be used as the starting point for analysis. API Extractor * analyzes the symbols exported by this module. * * The file extension must be ".d.ts" and not ".ts". * * The path is resolved relative to the folder of the config file that contains the setting; to change this, * prepend a folder token such as "". * * SUPPORTED TOKENS: , , */ "mainEntryPointFilePath": "/lib/index.d.ts", /** * A list of NPM package names whose exports should be treated as part of this package. * * For example, suppose that Webpack is used to generate a distributed bundle for the project "library1", * and another NPM package "library2" is embedded in this bundle. Some types from library2 may become part * of the exported API for library1, but by default API Extractor would generate a .d.ts rollup that explicitly * imports library2. To avoid this, we can specify: * * "bundledPackages": [ "library2" ], * * This would direct API Extractor to embed those types directly in the .d.ts rollup, as if they had been * local files for library1. */ "bundledPackages": [], /** * Determines how the TypeScript compiler engine will be invoked by API Extractor. */ "compiler": { /** * Specifies the path to the tsconfig.json file to be used by API Extractor when analyzing the project. * * The path is resolved relative to the folder of the config file that contains the setting; to change this, * prepend a folder token such as "". * * Note: This setting will be ignored if "overrideTsconfig" is used. * * SUPPORTED TOKENS: , , * DEFAULT VALUE: "/tsconfig.json" */ // "tsconfigFilePath": "/tsconfig.json", /** * Provides a compiler configuration that will be used instead of reading the tsconfig.json file from disk. * The object must conform to the TypeScript tsconfig schema: * * http://json.schemastore.org/tsconfig * * If omitted, then the tsconfig.json file will be read from the "projectFolder". * * DEFAULT VALUE: no overrideTsconfig section */ // "overrideTsconfig": { // . . . // } /** * This option causes the compiler to be invoked with the --skipLibCheck option. This option is not recommended * and may cause API Extractor to produce incomplete or incorrect declarations, but it may be required when * dependencies contain declarations that are incompatible with the TypeScript engine that API Extractor uses * for its analysis. Where possible, the underlying issue should be fixed rather than relying on skipLibCheck. * * DEFAULT VALUE: false */ // "skipLibCheck": true, }, /** * Configures how the API report file (*.api.md) will be generated. */ "apiReport": { /** * (REQUIRED) Whether to generate an API report. */ "enabled": true, /** * The filename for the API report files. It will be combined with "reportFolder" or "reportTempFolder" to produce * a full file path. * * The file extension should be ".api.md", and the string should not contain a path separator such as "\" or "/". * * SUPPORTED TOKENS: , * DEFAULT VALUE: ".api.md" */ // "reportFileName": ".api.md", /** * Specifies the folder where the API report file is written. The file name portion is determined by * the "reportFileName" setting. * * The API report file is normally tracked by Git. Changes to it can be used to trigger a branch policy, * e.g. for an API review. * * The path is resolved relative to the folder of the config file that contains the setting; to change this, * prepend a folder token such as "". * * SUPPORTED TOKENS: , , * DEFAULT VALUE: "/etc/" */ "reportFolder": "review/api", /** * Specifies the folder where the temporary report file is written. The file name portion is determined by * the "reportFileName" setting. * * After the temporary file is written to disk, it is compared with the file in the "reportFolder". * If they are different, a production build will fail. * * The path is resolved relative to the folder of the config file that contains the setting; to change this, * prepend a folder token such as "". * * SUPPORTED TOKENS: , , * DEFAULT VALUE: "/temp/" */ "reportTempFolder": "review/api/temp/" }, /** * Configures how the doc model file (*.api.json) will be generated. */ "docModel": { /** * (REQUIRED) Whether to generate a doc model file. */ "enabled": true, /** * The output path for the doc model file. The file extension should be ".api.json". * * The path is resolved relative to the folder of the config file that contains the setting; to change this, * prepend a folder token such as "". * * SUPPORTED TOKENS: , , * DEFAULT VALUE: "/temp/.api.json" */ "apiJsonFilePath": "review/api/temp/.api.json" }, /** * Configures how the .d.ts rollup file will be generated. */ "dtsRollup": { /** * (REQUIRED) Whether to generate the .d.ts rollup file. */ "enabled": false /** * Specifies the output path for a .d.ts rollup file to be generated without any trimming. * This file will include all declarations that are exported by the main entry point. * * If the path is an empty string, then this file will not be written. * * The path is resolved relative to the folder of the config file that contains the setting; to change this, * prepend a folder token such as "". * * SUPPORTED TOKENS: , , * DEFAULT VALUE: "/dist/.d.ts" */ // "untrimmedFilePath": "/dist/.d.ts", /** * Specifies the output path for a .d.ts rollup file to be generated with trimming for a "beta" release. * This file will include only declarations that are marked as "@public" or "@beta". * * The path is resolved relative to the folder of the config file that contains the setting; to change this, * prepend a folder token such as "". * * SUPPORTED TOKENS: , , * DEFAULT VALUE: "" */ // "betaTrimmedFilePath": "/dist/-beta.d.ts", /** * Specifies the output path for a .d.ts rollup file to be generated with trimming for a "public" release. * This file will include only declarations that are marked as "@public". * * If the path is an empty string, then this file will not be written. * * The path is resolved relative to the folder of the config file that contains the setting; to change this, * prepend a folder token such as "". * * SUPPORTED TOKENS: , , * DEFAULT VALUE: "" */ // "publicTrimmedFilePath": "/dist/-public.d.ts", /** * When a declaration is trimmed, by default it will be replaced by a code comment such as * "Excluded from this release type: exampleMember". Set "omitTrimmingComments" to true to remove the * declaration completely. * * DEFAULT VALUE: false */ // "omitTrimmingComments": true }, /** * Configures how the tsdoc-metadata.json file will be generated. */ "tsdocMetadata": { /** * Whether to generate the tsdoc-metadata.json file. * * DEFAULT VALUE: true */ // "enabled": true, /** * Specifies where the TSDoc metadata file should be written. * * The path is resolved relative to the folder of the config file that contains the setting; to change this, * prepend a folder token such as "". * * The default value is "", which causes the path to be automatically inferred from the "tsdocMetadata", * "typings" or "main" fields of the project's package.json. If none of these fields are set, the lookup * falls back to "tsdoc-metadata.json" in the package folder. * * SUPPORTED TOKENS: , , * DEFAULT VALUE: "" */ // "tsdocMetadataFilePath": "/dist/tsdoc-metadata.json" }, /** * Specifies what type of newlines API Extractor should use when writing output files. By default, the output files * will be written with Windows-style newlines. To use POSIX-style newlines, specify "lf" instead. * To use the OS's default newline kind, specify "os". * * DEFAULT VALUE: "crlf" */ // "newlineKind": "crlf", /** * Configures how API Extractor reports error and warning messages produced during analysis. * * There are three sources of messages: compiler messages, API Extractor messages, and TSDoc messages. */ "messages": { /** * Configures handling of diagnostic messages reported by the TypeScript compiler engine while analyzing * the input .d.ts files. * * TypeScript message identifiers start with "TS" followed by an integer. For example: "TS2551" * * DEFAULT VALUE: A single "default" entry with logLevel=warning. */ "compilerMessageReporting": { /** * Configures the default routing for messages that don't match an explicit rule in this table. */ "default": { /** * Specifies whether the message should be written to the the tool's output log. Note that * the "addToApiReportFile" property may supersede this option. * * Possible values: "error", "warning", "none" * * Errors cause the build to fail and return a nonzero exit code. Warnings cause a production build fail * and return a nonzero exit code. For a non-production build (e.g. when "api-extractor run" includes * the "--local" option), the warning is displayed but the build will not fail. * * DEFAULT VALUE: "warning" */ "logLevel": "warning" /** * When addToApiReportFile is true: If API Extractor is configured to write an API report file (.api.md), * then the message will be written inside that file; otherwise, the message is instead logged according to * the "logLevel" option. * * DEFAULT VALUE: false */ // "addToApiReportFile": false } // "TS2551": { // "logLevel": "warning", // "addToApiReportFile": true // }, // // . . . }, /** * Configures handling of messages reported by API Extractor during its analysis. * * API Extractor message identifiers start with "ae-". For example: "ae-extra-release-tag" * * DEFAULT VALUE: See api-extractor-defaults.json for the complete table of extractorMessageReporting mappings */ "extractorMessageReporting": { "default": { "logLevel": "warning" // "addToApiReportFile": false }, "ae-missing-release-tag": { "logLevel": "none" } // "ae-extra-release-tag": { // "logLevel": "warning", // "addToApiReportFile": true // }, // // . . . }, /** * Configures handling of messages reported by the TSDoc parser when analyzing code comments. * * TSDoc message identifiers start with "tsdoc-". For example: "tsdoc-link-tag-unescaped-text" * * DEFAULT VALUE: A single "default" entry with logLevel=warning. */ "tsdocMessageReporting": { "default": { "logLevel": "warning" // "addToApiReportFile": false } // "tsdoc-link-tag-unescaped-text": { // "logLevel": "warning", // "addToApiReportFile": true // }, // // . . . } } } lumino-2021.12.13/binder/000077500000000000000000000000001415564225700146645ustar00rootroot00000000000000lumino-2021.12.13/binder/README.md000066400000000000000000000004341415564225700161440ustar00rootroot00000000000000This directory holds configuration files for https://mybinder.org/. A Binder instance can be launched by visiting this URL: https://mybinder.org/v2/gh/jupyterlab/lumino/master To check out a different version, just replace "master" with the desired branch/tag name or commit hash. lumino-2021.12.13/binder/environment.yaml000066400000000000000000000001601415564225700201110ustar00rootroot00000000000000name: example-environment channels: - conda-forge dependencies: - jupyterlab-link-share=0.2 - nodejs=14.0 lumino-2021.12.13/binder/postBuild000066400000000000000000000001271415564225700165540ustar00rootroot00000000000000#!/bin/bash set -euo pipefail npm install -g yarn yarn yarn build yarn build:examples lumino-2021.12.13/binder/start000066400000000000000000000003541415564225700157460ustar00rootroot00000000000000#!/usr/bin/env python3 import sys import shutil import os argv = sys.argv[1:] + ['--LabApp.collaborative', 'true'] print(argv) with open('startup_args.txt', 'w') as fid: fid.write(str(argv)) os.execv(shutil.which(argv[0]), argv) lumino-2021.12.13/docs/000077500000000000000000000000001415564225700143515ustar00rootroot00000000000000lumino-2021.12.13/docs/Makefile000066400000000000000000000011761415564225700160160ustar00rootroot00000000000000# Minimal makefile for Sphinx documentation # # You can set these variables from the command line, and also # from the environment for the first two. SPHINXOPTS ?= SPHINXBUILD ?= sphinx-build SOURCEDIR = source BUILDDIR = build # Put it first so that "make" without argument is like "make help". help: @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) .PHONY: help Makefile # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) lumino-2021.12.13/docs/environment.yml000066400000000000000000000003731415564225700174430ustar00rootroot00000000000000 name: lumino_documentation channels: - conda-forge dependencies: - python=3.8 - sphinx>=1.8 - sphinx-copybutton - sphinx_rtd_theme - pytest - pip - nodejs - pip: - jsx-lexer - pytest-check-links[cache]>=0.4.3 - myst_parser - requests_cache lumino-2021.12.13/docs/make.bat000066400000000000000000000013741415564225700157630ustar00rootroot00000000000000@ECHO OFF pushd %~dp0 REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set SOURCEDIR=source set BUILDDIR=build if "%1" == "" goto help %SPHINXBUILD% >NUL 2>NUL if errorlevel 9009 ( echo. echo.The 'sphinx-build' command was not found. Make sure you have Sphinx echo.installed, then set the SPHINXBUILD environment variable to point echo.to the full path of the 'sphinx-build' executable. Alternatively you echo.may add the Sphinx directory to PATH. echo. echo.If you don't have Sphinx installed, grab it from echo.http://sphinx-doc.org/ exit /b 1 ) %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% goto end :help %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% :end popd lumino-2021.12.13/docs/source/000077500000000000000000000000001415564225700156515ustar00rootroot00000000000000lumino-2021.12.13/docs/source/_static/000077500000000000000000000000001415564225700172775ustar00rootroot00000000000000lumino-2021.12.13/docs/source/_static/css/000077500000000000000000000000001415564225700200675ustar00rootroot00000000000000lumino-2021.12.13/docs/source/_static/css/custom.css000066400000000000000000000026371415564225700221230ustar00rootroot00000000000000h4 { font-size: 100%; } .wy-nav-side p.caption { color: #f5f5f5; } div.wy-side-nav-search { background: #f37626; } .wy-nav-content iframe { margin: auto; display: block; } .wy-breadcrumbs-aside img { height: 1em !important; } /* Elevation * * We style box-shadows using Material Design's idea of elevation. These particular numbers are taken from here: * * https://github.com/material-components/material-components-web * https://material-components-web.appspot.com/elevation.html */ .rst-content img.jp-screenshot { border: none; /* MD Elevation 8 */ box-shadow: 0px 5px 5px -3px rgba(0, 0, 0, 0.2), 0px 8px 10px 1px rgba(145, 145, 145, 0.14), 0px 3px 14px 2px rgba(0, 0, 0, 0.12); margin-bottom: 24px; } /* * The div.jp-youtube-video styling is done to get the YouTube video to size dynamically * to 100% of the content width. */ .rst-content div.jp-youtube-video { position: relative; width: 100%; height: 0px; /* This must be equal to the inverse of the aspect ratio of the video */ /* The current value is: 56.25% = 315/560 */ padding-bottom: 56.25%; border: none; /* MD Elevation 8 */ box-shadow: 0px 5px 5px -3px rgba(0, 0, 0, 0.2), 0px 8px 10px 1px rgba(0, 0, 0, 0.14), 0px 3px 14px 2px rgba(0, 0, 0, 0.12); margin-bottom: 24px; } .rst-content div.jp-youtube-video iframe { position: absolute; left: 0px; top: 0px; width: 100%; height: 100%; } lumino-2021.12.13/docs/source/_static/jupyter_logo.svg000066400000000000000000000100361415564225700225420ustar00rootroot00000000000000 logo-5.svg Created using Figma 0.90 lumino-2021.12.13/docs/source/_templates/000077500000000000000000000000001415564225700200065ustar00rootroot00000000000000lumino-2021.12.13/docs/source/_templates/breadcrumbs.html000066400000000000000000000047661415564225700232020ustar00rootroot00000000000000{% extends '!breadcrumbs.html' %} {% block breadcrumbs %}
  • {{ _('Docs') }} »
  • {% for doc in parents %}
  • {{ doc.title }} »
  • {% endfor %}
  • {{ title }}
  • {% endblock %} {% block breadcrumbs_aside %}
  • {% if hasdoc(pagename) %} {% if display_github %} {% if check_meta and 'github_url' in meta %} {{ _('Edit on GitHub') }} {% else %} {{ _('Edit on GitHub') }} {% endif %} {% elif display_bitbucket %} {% if check_meta and 'bitbucket_url' in meta %} {{ _('Edit on Bitbucket') }} {% else %} {{ _('Edit on Bitbucket') }} {% endif %} {% elif display_gitlab %} {% if check_meta and 'gitlab_url' in meta %} {{ _('Edit on GitLab') }} {% else %} {{ _('Edit on GitLab') }} {% endif %} {% elif show_source and source_url_prefix %} {{ _('View page source') }} {% elif show_source and has_source and sourcename %} {{ _('View page source') }} {% endif %} {% endif %}
  • {{ _('Jupyter') }} |  
  • {% endblock %} lumino-2021.12.13/docs/source/_templates/footer.html000066400000000000000000000040401415564225700221700ustar00rootroot00000000000000
    {% if (theme_prev_next_buttons_location == 'bottom' or theme_prev_next_buttons_location == 'both') and (next or prev) %} {% endif %}

    {%- if show_copyright %} {%- if hasdoc('copyright') %} {% trans path=pathto('copyright'), copyright=copyright|e %}© Copyright {{ copyright }}.{% endtrans %} {%- else %} {% trans copyright=copyright|e %}© Copyright {{ copyright }}.
    The Jupyter Trademark is registered with the U.S. Patent & Trademark Office. {% endtrans %} {%- endif %} {%- endif %} {%- if build_id and build_url %} {% trans build_url=build_url, build_id=build_id %} Build {{ build_id }}. {% endtrans %} {%- elif commit %} {% trans commit=commit %} Revision {{ commit }}. {% endtrans %} {%- elif last_updated %} {% trans last_updated=last_updated|e %}Last updated on {{ last_updated }}.{% endtrans %} {%- endif %}

    {%- if show_sphinx %} {% trans %}Built with Sphinx using a theme provided by Read the Docs{% endtrans %}. {%- endif %} {%- block extrafooter %} {% endblock %}
    lumino-2021.12.13/docs/source/api.rst000066400000000000000000000004531415564225700171560ustar00rootroot00000000000000Lumino API Reference ======================== .. this doc exists as a resolvable link target .. which statically included files are not .. meta:: :http-equiv=refresh: 0;url=./api/index.html The Lumino API reference docs are `here <./api/index.html>`_ if you are not redirected automatically. lumino-2021.12.13/docs/source/api_index.html000066400000000000000000000273231415564225700205060ustar00rootroot00000000000000 @lumino
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    @lumino

    Legend

    • Namespace
    • Variable
    • Function
    • Function with type parameter
    • Type alias
    • Type alias with type parameter
    • Interface
    • Interface with type parameter
    • Class
    • Class with type parameter

    Generated using TypeDoc

    lumino-2021.12.13/docs/source/conf.py000066400000000000000000000221731415564225700171550ustar00rootroot00000000000000#!/usr/bin/env python3 # -*- coding: utf-8 -*- # # Lumino documentation build configuration file, created by # sphinx-quickstart on Thu Jan 4 15:10:23 2018. # # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # # import os # import sys # sys.path.insert(0, os.path.abspath('.')) import json from pathlib import Path import os import os.path as osp import shutil from subprocess import check_call HERE = osp.abspath(osp.dirname(__file__)) EXAMPLES = ["accordionpanel", "datagrid", "dockpanel"] # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. # # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ 'myst_parser', 'sphinx.ext.intersphinx', 'sphinx.ext.mathjax', 'sphinx_copybutton' ] myst_enable_extensions = ["html_image"] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The file extensions of source files. # Sphinx considers the files with this suffix as sources. # The value can be a dictionary mapping file extensions to file types. source_suffix = { '.rst': 'restructuredtext', '.md': 'markdown' } # The master toctree document. master_doc = 'index' # General information about the project. project = 'Lumino' copyright = '2021, Project Jupyter' author = 'Project Jupyter' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. package_json = Path(osp.join(HERE, '..', '..', 'package.json')) data = json.loads(package_json.read_text(encoding='utf-8')) version = release = data['version'] # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. language = None # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This patterns also effect to html_static_path and html_extra_path exclude_patterns = [] # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = False # build js docs and stage them to the build directory def build_api_docs(out_dir): """build js api docs""" docs = osp.join(HERE, os.pardir) root = osp.join(docs, os.pardir) docs_api = osp.join(docs, "source", "api") api_index = osp.join(docs_api, "algorithm", "index.html") if osp.exists(api_index): # avoid rebuilding docs because it takes forever # `make clean` to force a rebuild print(f"already have {api_index}") else: print("Building lumino API docs") npm = [shutil.which('npm')] check_call(npm + ['install', '-g', 'yarn'], cwd=root) yarn = [shutil.which('yarn')] check_call(yarn, cwd=root) check_call(yarn + ["docs"], cwd=root) dest_dir = osp.join(out_dir, "api") print(f"Copying {docs_api} -> {dest_dir}") if osp.exists(dest_dir): shutil.rmtree(dest_dir) shutil.copytree(docs_api, dest_dir) shutil.copy(osp.join(HERE, 'api_index.html'), osp.join(dest_dir, 'index.html')) # build js examples and stage them to the build directory def build_examples(out_dir): """build js example docs""" docs = osp.join(HERE, os.pardir) root = osp.join(docs, os.pardir) examples_dir = osp.join(docs, "source", "examples") example_index = osp.join(examples_dir, EXAMPLES[0], "index.html") if osp.exists(example_index): # avoid rebuilding examples because it takes forever # `make clean` to force a rebuild print(f"already have examples") else: print("Building lumino examples") npm = [shutil.which('npm')] check_call(npm + ['install', '-g', 'yarn'], cwd=root) yarn = [shutil.which('yarn')] check_call(yarn, cwd=root) check_call(yarn + ["build"], cwd=root) check_call(yarn + ["build:examples"], cwd=root) # Copy the examples into source so the JS files get picked up for example in EXAMPLES: source = osp.join(root, "examples", f"example-{example}") dest_dir = osp.join(docs, "source", "examples", example) print(f"Copying {source} -> {dest_dir}") if osp.exists(dest_dir): shutil.rmtree(dest_dir) shutil.copytree(source, dest_dir) dest_dir = osp.join(out_dir, "examples") print(f"Copying {examples_dir} -> {dest_dir}") if osp.exists(dest_dir): shutil.rmtree(dest_dir) shutil.copytree(examples_dir, dest_dir) # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # import sphinx_rtd_theme html_theme = "sphinx_rtd_theme" html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # # html_theme_options = {} # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # Custom sidebar templates, must be a dictionary that maps document names # to template names. # # This is required for the alabaster theme # refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars html_sidebars = { '**': [ 'about.html', 'navigation.html', 'relations.html', # needs 'show_related': True theme option to display 'searchbox.html', 'donate.html', ] } # Output for github to be used in links html_context = { "display_github": True, # Integrate GitHub "github_user": "jupyterlab", # Username "github_repo": "lumino", # Repo name "github_version": "master", # Version "conf_py_path": "/docs/source/", # Path in the checkout to the docs root } # -- Options for HTMLHelp output ------------------------------------------ # Output file base name for HTML help builder. htmlhelp_basename = 'Luminodoc' # -- Options for LaTeX output --------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # # 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). # # 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. # # 'preamble': '', # Latex figure (float) alignment # # 'figure_align': 'htbp', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ (master_doc, 'Lumino.tex', 'Lumino Documentation', 'Project Jupyter', 'manual'), ] # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ (master_doc, 'lumino', 'Lumino Documentation', [author], 1) ] # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ (master_doc, 'Lumino', 'Lumino Documentation', author, 'Lumino', 'One line description of project.', 'Miscellaneous'), ] # -- Options for Epub output ---------------------------------------------- # Bibliographic Dublin Core info. epub_title = project epub_author = author epub_publisher = author epub_copyright = copyright # The unique identifier of the text. This can be a ISBN number # or the project homepage. # # epub_identifier = '' # A unique identification for the text. # # epub_uid = '' # A list of files that should not be packed into the epub file. epub_exclude_files = ['search.html'] # Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = {'https://docs.python.org/': None} def setup(app): dest = osp.join(HERE, 'changelog.md') shutil.copy(osp.join(HERE, '..', '..', 'CHANGELOG.md'), dest) app.add_css_file('css/custom.css') # may also be an URL build_api_docs(app.outdir) build_examples(app.outdir) lumino-2021.12.13/docs/source/examples.rst000066400000000000000000000003011415564225700202130ustar00rootroot00000000000000 Examples ======== Rendered static examples `Accordion Panel `_ `DataGrid `_ `DockPanel `_ lumino-2021.12.13/docs/source/index.rst000066400000000000000000000006311415564225700175120ustar00rootroot00000000000000.. Lumino documentation master file, created by sphinx-quickstart on Sun Mar 21 05:42:16 2021. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. Lumino ====== .. toctree:: :maxdepth: 1 :caption: Contents: changelog api examples Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` lumino-2021.12.13/examples/000077500000000000000000000000001415564225700152375ustar00rootroot00000000000000lumino-2021.12.13/examples/example-accordionpanel/000077500000000000000000000000001415564225700216515ustar00rootroot00000000000000lumino-2021.12.13/examples/example-accordionpanel/index.html000066400000000000000000000003631415564225700236500ustar00rootroot00000000000000 lumino-2021.12.13/examples/example-accordionpanel/package.json000066400000000000000000000011121415564225700241320ustar00rootroot00000000000000{ "name": "@lumino/example-accordionpanel", "version": "0.6.1", "private": true, "scripts": { "build": "tsc && webpack", "clean": "rimraf build" }, "dependencies": { "@lumino/default-theme": "^0.20.2", "@lumino/messaging": "^1.10.1", "@lumino/widgets": "^1.30.1", "es6-promise": "^4.0.5" }, "devDependencies": { "css-loader": "^3.4.0", "file-loader": "^5.0.2", "rimraf": "^3.0.2", "source-map-loader": "0.2.4", "style-loader": "^1.0.2", "typescript": "~3.6.0", "webpack": "^4.41.3", "webpack-cli": "^3.3.10" } } lumino-2021.12.13/examples/example-accordionpanel/src/000077500000000000000000000000001415564225700224405ustar00rootroot00000000000000lumino-2021.12.13/examples/example-accordionpanel/src/index.ts000066400000000000000000000033671415564225700241300ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. import 'es6-promise/auto'; // polyfill Promise on IE import { Message } from '@lumino/messaging'; import { AccordionPanel, BoxPanel, Widget } from '@lumino/widgets'; import '../style/index.css'; class ContentWidget extends Widget { static createNode(): HTMLElement { let node = document.createElement('div'); let content = document.createElement('div'); let input = document.createElement('input'); input.placeholder = 'Placeholder...'; content.appendChild(input); node.appendChild(content); return node; } constructor(name: string) { super({ node: ContentWidget.createNode() }); this.setFlag(Widget.Flag.DisallowLayout); this.addClass('content'); this.addClass(name.toLowerCase()); this.title.label = name; this.title.closable = true; this.title.caption = `Long description for: ${name}`; } get inputNode(): HTMLInputElement { return this.node.getElementsByTagName('input')[0] as HTMLInputElement; } protected onActivateRequest(msg: Message): void { if (this.isAttached) { this.inputNode.focus(); } } } function main(): void { const accordion = new AccordionPanel(); accordion.id = 'accordion'; const r1 = new ContentWidget('Red'); const b1 = new ContentWidget('Blue'); const g1 = new ContentWidget('Green'); accordion.addWidget(r1); accordion.addWidget(b1); accordion.addWidget(g1); BoxPanel.setStretch(accordion, 1); const main = new BoxPanel({ direction: 'left-to-right', spacing: 0 }); main.id = 'main'; main.addWidget(accordion); window.onresize = () => { main.update(); }; Widget.attach(main, document.body); } window.onload = main; lumino-2021.12.13/examples/example-accordionpanel/style/000077500000000000000000000000001415564225700230115ustar00rootroot00000000000000lumino-2021.12.13/examples/example-accordionpanel/style/content.css000066400000000000000000000013721415564225700252000ustar00rootroot00000000000000/* Copyright (c) Jupyter Development Team. Distributed under the terms of the Modified BSD License. */ .content { min-width: 50px; min-height: 50px; display: flex; flex-direction: column; padding: 8px; border: 1px solid #c0c0c0; background: white; box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.2); } .lm-AccordionPanel[data-orientation='vertical'] .content { border-top: none; } .lm-AccordionPanel[data-orientation='horizontal'] .content { border-left: none; } .content > div { flex: 1 1 auto; border: 1px solid #505050; overflow: auto; } .content input { margin: 8px; } .red > div { background: #e74c3c; } .yellow > div { background: #f1c40f; } .green > div { background: #27ae60; } .blue > div { background: #3498db; } lumino-2021.12.13/examples/example-accordionpanel/style/index.css000066400000000000000000000006541415564225700246370ustar00rootroot00000000000000/* Copyright (c) Jupyter Development Team. Distributed under the terms of the Modified BSD License. */ @import '~@lumino/default-theme/style/index.css'; @import './content.css'; body { display: flex; flex-direction: column; position: absolute; top: 0; left: 0; right: 0; bottom: 0; margin: 0; padding: 0; overflow: hidden; } #main { flex: 1 1 auto; } #accordion { flex: 1 1 auto; padding: 4px; } lumino-2021.12.13/examples/example-accordionpanel/tsconfig.json000066400000000000000000000006201415564225700243560ustar00rootroot00000000000000{ "compilerOptions": { "declaration": false, "noImplicitAny": true, "noEmitOnError": true, "noUnusedLocals": true, "strictNullChecks": true, "sourceMap": true, "module": "commonjs", "moduleResolution": "node", "target": "ES5", "outDir": "./build", "lib": ["ES5", "ES2015.Promise", "ES2015.Iterable", "DOM"], "types": [] }, "include": ["src/*"] } lumino-2021.12.13/examples/example-accordionpanel/webpack.config.js000066400000000000000000000007041415564225700250700ustar00rootroot00000000000000var path = require('path'); module.exports = { entry: './build/index.js', mode: 'development', devtool: 'source-map', output: { path: __dirname + '/build/', filename: 'bundle.example.js', publicPath: './build/' }, module: { rules: [ { test: /\.js$/, use: ['source-map-loader'], enforce: 'pre' }, { test: /\.css$/, use: ['style-loader', 'css-loader'] }, { test: /\.png$/, use: 'file-loader' } ] } }; lumino-2021.12.13/examples/example-datagrid/000077500000000000000000000000001415564225700204475ustar00rootroot00000000000000lumino-2021.12.13/examples/example-datagrid/index.html000066400000000000000000000002061415564225700224420ustar00rootroot00000000000000 lumino-2021.12.13/examples/example-datagrid/package.json000066400000000000000000000011051415564225700227320ustar00rootroot00000000000000{ "name": "@lumino/example-datagrid", "version": "0.30.1", "private": true, "scripts": { "build": "tsc && webpack", "clean": "rimraf build" }, "dependencies": { "@lumino/datagrid": "^0.34.1", "@lumino/default-theme": "^0.20.2", "@lumino/dragdrop": "^1.13.1", "@lumino/widgets": "^1.30.1", "es6-promise": "^4.0.5" }, "devDependencies": { "css-loader": "^3.4.0", "file-loader": "^5.0.2", "rimraf": "^3.0.2", "style-loader": "^1.0.2", "typescript": "~3.6.0", "webpack": "^4.41.3", "webpack-cli": "^3.3.10" } } lumino-2021.12.13/examples/example-datagrid/src/000077500000000000000000000000001415564225700212365ustar00rootroot00000000000000lumino-2021.12.13/examples/example-datagrid/src/index.ts000066400000000000000000001210021415564225700227110ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import 'es6-promise/auto'; // polyfill Promise on IE import { BasicKeyHandler, BasicMouseHandler, BasicSelectionModel, CellEditor, CellGroup, CellRenderer, DataGrid, DataModel, HyperlinkRenderer, ICellEditor, JSONModel, MutableDataModel, TextRenderer } from '@lumino/datagrid'; import { DockPanel, StackedPanel, Widget } from '@lumino/widgets'; import { getKeyboardLayout } from '@lumino/keyboard'; import '../style/index.css'; class MergedCellModel extends DataModel { rowCount(region: DataModel.RowRegion): number { return region === 'body' ? 20 : 3; } columnCount(region: DataModel.ColumnRegion): number { return region === 'body' ? 6 : 3; } data(region: DataModel.CellRegion, row: number, column: number): any { if (region === 'row-header') { return `R: ${row}, ${column}`; } if (region === 'column-header') { return `C: ${row}, ${column}`; } if (region === 'corner-header') { return `N: ${row}, ${column}`; } return `(${row}, ${column})`; } groupCount(region: DataModel.RowRegion): number { if (region === 'body') { return 3; } else if (region === 'column-header') { return 1; } else if (region === 'row-header') { return 2; } else if (region === 'corner-header') { return 1; } return 0; } group(region: DataModel.CellRegion, groupIndex: number): CellGroup | null { if (region === 'body') { return [ { r1: 1, c1: 1, r2: 2, c2: 2 }, { r1: 5, c1: 1, r2: 5, c2: 2 }, { r1: 3, c1: 5, r2: 4, c2: 5 } ][groupIndex]; } if (region === 'column-header') { return [{ r1: 0, c1: 4, r2: 1, c2: 4 }][groupIndex]; } if (region === 'row-header') { return [ { r1: 0, c1: 0, r2: 1, c2: 1 }, { r1: 4, c1: 0, r2: 5, c2: 0 } ][groupIndex]; } if (region === 'corner-header') { return [{ r1: 0, c1: 0, r2: 1, c2: 1 }][groupIndex]; } return null; } } class LargeDataModel extends DataModel { rowCount(region: DataModel.RowRegion): number { return region === 'body' ? 1000000000000 : 2; } columnCount(region: DataModel.ColumnRegion): number { return region === 'body' ? 1000000000000 : 3; } data(region: DataModel.CellRegion, row: number, column: number): any { if (region === 'row-header') { return `R: ${row}, ${column}`; } if (region === 'column-header') { return `C: ${row}, ${column}`; } if (region === 'corner-header') { return `N: ${row}, ${column}`; } return `(${row}, ${column})`; } } class StreamingDataModel extends DataModel { static createRow(n: number): number[] { let row = new Array(n); for (let i = 0; i < n; ++i) { row[i] = Math.random(); } return row; } constructor() { super(); setInterval(this._tick, 250); } rowCount(region: DataModel.RowRegion): number { return region === 'body' ? this._data.length : 1; } columnCount(region: DataModel.ColumnRegion): number { return region === 'body' ? 50 : 1; } data(region: DataModel.CellRegion, row: number, column: number): any { if (region === 'row-header') { return `R: ${row}, ${column}`; } if (region === 'column-header') { return `C: ${row}, ${column}`; } if (region === 'corner-header') { return `N: ${row}, ${column}`; } return this._data[row][column]; } private _tick = () => { let nr = this.rowCount('body'); let nc = this.columnCount('body'); let r1 = Math.random(); let r2 = Math.random(); let i = Math.floor(r2 * nr); if ((r1 < 0.45 && nr > 4) || nr >= 500) { this._data.splice(i, 1); this.emitChanged({ type: 'rows-removed', region: 'body', index: i, span: 1 }); } else { this._data.splice(i, 0, StreamingDataModel.createRow(nc)); this.emitChanged({ type: 'rows-inserted', region: 'body', index: i, span: 1 }); } }; private _data: number[][] = []; } class RandomDataModel extends DataModel { static genPoint(): number { return Math.random() * 10 - 2; } constructor(rowCount: number, columnCount: number) { super(); let nr = (this._rowCount = rowCount); let nc = (this._columnCount = columnCount); for (let i = 0, n = nr * nc; i < n; ++i) { this._data[i] = i / n; } setInterval(this._tick, 60); } rowCount(region: DataModel.RowRegion): number { return region === 'body' ? this._rowCount : 1; } columnCount(region: DataModel.ColumnRegion): number { return region === 'body' ? this._columnCount : 1; } data(region: DataModel.CellRegion, row: number, column: number): any { if (region === 'row-header') { return `R: ${row}, ${column}`; } if (region === 'column-header') { return `C: ${row}, ${column}`; } if (region === 'corner-header') { return `N: ${row}, ${column}`; } return this._data[row * this._columnCount + column]; } private _tick = () => { let nr = this._rowCount; let nc = this._columnCount; let i = Math.floor(Math.random() * (nr * nc - 1)); let r = Math.floor(i / nc); let c = i - r * nc; this._data[i] = (this._data[i] + Math.random()) % 1; this.emitChanged({ type: 'cells-changed', region: 'body', row: r, column: c, rowSpan: 1, columnSpan: 1 }); }; private _rowCount: number; private _columnCount: number; private _data: number[] = []; } /** * Example custom cell editor which implements editing * JSON cell data. */ class JSONCellEditor extends CellEditor { dispose(): void { if (this.isDisposed) { return; } super.dispose(); document.body.removeChild(this._textarea); } protected startEditing() { this._createWidgets(); } protected getInput(): any { return JSON.parse(this._textarea.value); } protected validate() { let value; try { value = this.getInput(); } catch (error) { console.log(`Input error: ${error.message}`); this.setValidity(false); return; } if (this.validator) { const result = this.validator.validate(this.cell, value); if (result.valid) { this.setValidity(true); } else { this.setValidity(false, result.message || 'Invalid JSON input'); } } else { this.setValidity(true); } } private _createWidgets() { const cell = this.cell; const grid = this.cell.grid; if (!grid.dataModel) { this.cancel(); return; } let data = grid.dataModel.data('body', cell.row, cell.column); const button = document.createElement('button'); button.type = 'button'; button.classList.add('cell-editor'); button.style.width = '100%'; button.style.height = '100%'; button.style.whiteSpace = 'nowrap'; button.style.overflow = 'hidden'; button.style.textOverflow = 'ellipsis'; button.textContent = this._deserialize(data); this.editorContainer.appendChild(button); this._button = button; const width = 200; const height = 50; const textarea = document.createElement('textarea'); textarea.style.pointerEvents = 'auto'; textarea.style.position = 'absolute'; textarea.style.outline = 'none'; const buttonRect = this._button.getBoundingClientRect(); const top = buttonRect.bottom + 2; const left = buttonRect.left; textarea.style.top = top + 'px'; textarea.style.left = left + 'px'; textarea.style.width = width + 'px'; textarea.style.height = height + 'px'; textarea.value = JSON.stringify(data); textarea.addEventListener('keydown', (event: KeyboardEvent) => { const key = getKeyboardLayout().keyForKeydownEvent(event); if (key === 'Enter' || key === 'Tab') { const next = key === 'Enter' ? event.shiftKey ? 'up' : 'down' : event.shiftKey ? 'left' : 'right'; if (!this.commit(next)) { this.setValidity(false); } event.preventDefault(); event.stopPropagation(); } else if (key === 'Escape') { this.cancel(); } }); textarea.addEventListener('blur', (event: FocusEvent) => { if (this.isDisposed) { return; } if (!this.commit()) { this.setValidity(false); } }); textarea.addEventListener('input', (event: Event) => { this.inputChanged.emit(void 0); }); this._textarea = textarea; document.body.appendChild(this._textarea); this._textarea.focus(); } private _deserialize(value: any): any { return JSON.stringify(value); } private _button: HTMLButtonElement; private _textarea: HTMLTextAreaElement; } /** * Mutable JSON data model. Allows editing existing * grid cell values via MutableDataModel interface. */ class MutableJSONModel extends MutableDataModel { constructor(options: JSONModel.IOptions) { super(); this._jsonModel = new JSONModel(options); } rowCount(region: DataModel.RowRegion): number { return this._jsonModel.rowCount(region); } columnCount(region: DataModel.ColumnRegion): number { return this._jsonModel.columnCount(region); } metadata( region: DataModel.CellRegion, row: number, column: number ): DataModel.Metadata { return this._jsonModel.metadata(region, row, column); } data(region: DataModel.CellRegion, row: number, column: number): any { return this._jsonModel.data(region, row, column); } setData( region: DataModel.CellRegion, row: number, column: number, value: any ): boolean { const model = this._jsonModel as any; // Set up the field and value variables. let field: JSONModel.Field; // Look up the field and value for the region. switch (region) { case 'body': field = model._bodyFields[column]; model._data[row][field.name] = value; break; default: throw 'cannot change header data'; } this.emitChanged({ type: 'cells-changed', region: 'body', row: row, column: column, rowSpan: 1, columnSpan: 1 }); return true; } private _jsonModel: JSONModel; } const redGreenBlack: CellRenderer.ConfigFunc = ({ value }) => { if (value <= 1 / 3) { return '#000000'; } if (value <= 2 / 3) { return '#FF0000'; } return '#009B00'; }; const heatMapViridis: CellRenderer.ConfigFunc = ({ value }) => { let r = Math.floor(ViridisCM.red(value) * 255); let g = Math.floor(ViridisCM.green(value) * 255); let b = Math.floor(ViridisCM.blue(value) * 255); return `rgb(${r}, ${g}, ${b})`; }; const heatMapViridisInverse: CellRenderer.ConfigFunc = ({ value }) => { let r = Math.floor(255 - ViridisCM.red(value) * 255); let g = Math.floor(255 - ViridisCM.green(value) * 255); let b = Math.floor(255 - ViridisCM.blue(value) * 255); return `rgb(${r}, ${g}, ${b})`; }; function createWrapper(content: Widget, title: string): Widget { let wrapper = new StackedPanel(); wrapper.addClass('content-wrapper'); wrapper.addWidget(content); wrapper.title.label = title; return wrapper; } function main(): void { let model1 = new LargeDataModel(); let model2 = new StreamingDataModel(); let model3 = new RandomDataModel(15, 10); let model4 = new RandomDataModel(80, 80); let model5 = new JSONModel(Data.cars); let model6 = new MutableJSONModel(Data.editable_test_data); let model7 = new MergedCellModel(); let blueStripeStyle: DataGrid.Style = { ...DataGrid.defaultStyle, rowBackgroundColor: i => (i % 2 === 0 ? 'rgba(138, 172, 200, 0.3)' : ''), columnBackgroundColor: i => (i % 2 === 0 ? 'rgba(100, 100, 100, 0.1)' : '') }; let brownStripeStyle: DataGrid.Style = { ...DataGrid.defaultStyle, columnBackgroundColor: i => (i % 2 === 0 ? 'rgba(165, 143, 53, 0.2)' : '') }; let greenStripeStyle: DataGrid.Style = { ...DataGrid.defaultStyle, rowBackgroundColor: i => (i % 2 === 0 ? 'rgba(64, 115, 53, 0.2)' : '') }; let lightSelectionStyle: DataGrid.Style = { ...DataGrid.defaultStyle, selectionFillColor: 'rgba(255, 255, 255, 0.2)', selectionBorderColor: 'white', headerSelectionBorderColor: 'white', cursorBorderColor: 'white' }; let fgColorFloatRenderer = new TextRenderer({ font: 'bold 12px sans-serif', textColor: redGreenBlack, format: TextRenderer.formatFixed({ digits: 2 }), horizontalAlignment: 'right' }); let bgColorFloatRenderer = new TextRenderer({ textColor: heatMapViridisInverse, backgroundColor: heatMapViridis, format: TextRenderer.formatFixed({ digits: 2 }), horizontalAlignment: 'right' }); let elideFloatRenderer = new TextRenderer({ elideDirection: ({ column }) => (column % 2 === 0 ? 'right' : 'left') }); let grid1 = new DataGrid({ style: blueStripeStyle }); grid1.dataModel = model1; grid1.keyHandler = new BasicKeyHandler(); grid1.mouseHandler = new BasicMouseHandler(); grid1.selectionModel = new BasicSelectionModel({ dataModel: model1 }); let grid2 = new DataGrid({ style: brownStripeStyle }); grid2.cellRenderers.update({ body: elideFloatRenderer }); grid2.dataModel = model2; grid2.keyHandler = new BasicKeyHandler(); grid2.mouseHandler = new BasicMouseHandler(); grid2.selectionModel = new BasicSelectionModel({ dataModel: model2, selectionMode: 'column' }); let grid3 = new DataGrid({ stretchLastColumn: true, minimumSizes: { rowHeight: 25, columnWidth: 70, rowHeaderWidth: 70, columnHeaderHeight: 25 } }); grid3.cellRenderers.update({ body: fgColorFloatRenderer }); grid3.dataModel = model3; grid3.keyHandler = new BasicKeyHandler(); grid3.mouseHandler = new BasicMouseHandler(); grid3.selectionModel = new BasicSelectionModel({ dataModel: model3 }); let grid4 = new DataGrid({ style: lightSelectionStyle }); grid4.cellRenderers.update({ body: bgColorFloatRenderer }); grid4.dataModel = model4; grid4.keyHandler = new BasicKeyHandler(); grid4.mouseHandler = new BasicMouseHandler(); grid4.selectionModel = new BasicSelectionModel({ dataModel: model4 }); let grid5 = new DataGrid({ style: greenStripeStyle, defaultSizes: { rowHeight: 32, columnWidth: 128, rowHeaderWidth: 64, columnHeaderHeight: 32 } }); grid5.dataModel = model5; grid5.keyHandler = new BasicKeyHandler(); grid5.mouseHandler = new BasicMouseHandler(); grid5.selectionModel = new BasicSelectionModel({ dataModel: model5, selectionMode: 'row' }); let grid6 = new DataGrid({ defaultSizes: { rowHeight: 32, columnWidth: 90, rowHeaderWidth: 64, columnHeaderHeight: 32 } }); grid6.dataModel = model6; grid6.keyHandler = new BasicKeyHandler(); grid6.mouseHandler = new BasicMouseHandler(); grid6.selectionModel = new BasicSelectionModel({ dataModel: model6, selectionMode: 'cell' }); grid6.editingEnabled = true; const columnIdentifier = { name: 'Corp. Data' }; grid6.editorController!.setEditor( columnIdentifier, (config: CellEditor.CellConfig): ICellEditor => { return new JSONCellEditor(); } ); const hyperlinkRenderer = new HyperlinkRenderer({ url: (config: CellRenderer.CellConfig) => { return config.value[0]; }, urlName: (config: CellRenderer.CellConfig) => { return config.value[1]; } }); grid6.cellRenderers.update({ body: (config: CellRenderer.CellConfig) => { if (config.metadata.name === 'link') { return hyperlinkRenderer; } return undefined; } }); let grid7 = new DataGrid(); grid7.dataModel = model6; let grid8 = new DataGrid(); grid8.dataModel = model7; grid8.keyHandler = new BasicKeyHandler(); grid8.mouseHandler = new BasicMouseHandler(); grid8.selectionModel = new BasicSelectionModel({ dataModel: model7, selectionMode: 'cell' }); let wrapper1 = createWrapper(grid1, 'Trillion Rows/Cols'); let wrapper2 = createWrapper(grid2, 'Streaming Rows'); let wrapper3 = createWrapper(grid3, 'Random Ticks 1'); let wrapper4 = createWrapper(grid4, 'Random Ticks 2'); let wrapper5 = createWrapper(grid5, 'JSON Data'); let wrapper6 = createWrapper(grid6, 'Editable Grid'); let wrapper7 = createWrapper(grid7, 'Copy'); let wrapper8 = createWrapper(grid8, 'Merged Cells'); let dock = new DockPanel(); dock.id = 'dock'; dock.addWidget(wrapper1); dock.addWidget(wrapper2, { mode: 'split-right', ref: wrapper1 }); dock.addWidget(wrapper3, { mode: 'split-bottom', ref: wrapper1 }); dock.addWidget(wrapper4, { mode: 'split-bottom', ref: wrapper2 }); dock.addWidget(wrapper5, { mode: 'split-bottom', ref: wrapper2 }); dock.addWidget(wrapper6, { mode: 'tab-before', ref: wrapper1 }); dock.addWidget(wrapper7, { mode: 'split-bottom', ref: wrapper6 }); dock.addWidget(wrapper8, { mode: 'tab-after', ref: wrapper1 }); dock.activateWidget(wrapper6); window.onresize = () => { dock.update(); }; Widget.attach(dock, document.body); } window.onload = main; namespace ViridisCM { // Color table from: // https://github.com/matplotlib/matplotlib/blob/38be7aeaaac3691560aeadafe46722dda427ef47/lib/matplotlib/_cm_listed.py const colorTable = [ [0.267004, 0.004874, 0.329415], [0.26851, 0.009605, 0.335427], [0.269944, 0.014625, 0.341379], [0.271305, 0.019942, 0.347269], [0.272594, 0.025563, 0.353093], [0.273809, 0.031497, 0.358853], [0.274952, 0.037752, 0.364543], [0.276022, 0.044167, 0.370164], [0.277018, 0.050344, 0.375715], [0.277941, 0.056324, 0.381191], [0.278791, 0.062145, 0.386592], [0.279566, 0.067836, 0.391917], [0.280267, 0.073417, 0.397163], [0.280894, 0.078907, 0.402329], [0.281446, 0.08432, 0.407414], [0.281924, 0.089666, 0.412415], [0.282327, 0.094955, 0.417331], [0.282656, 0.100196, 0.42216], [0.28291, 0.105393, 0.426902], [0.283091, 0.110553, 0.431554], [0.283197, 0.11568, 0.436115], [0.283229, 0.120777, 0.440584], [0.283187, 0.125848, 0.44496], [0.283072, 0.130895, 0.449241], [0.282884, 0.13592, 0.453427], [0.282623, 0.140926, 0.457517], [0.28229, 0.145912, 0.46151], [0.281887, 0.150881, 0.465405], [0.281412, 0.155834, 0.469201], [0.280868, 0.160771, 0.472899], [0.280255, 0.165693, 0.476498], [0.279574, 0.170599, 0.479997], [0.278826, 0.17549, 0.483397], [0.278012, 0.180367, 0.486697], [0.277134, 0.185228, 0.489898], [0.276194, 0.190074, 0.493001], [0.275191, 0.194905, 0.496005], [0.274128, 0.199721, 0.498911], [0.273006, 0.20452, 0.501721], [0.271828, 0.209303, 0.504434], [0.270595, 0.214069, 0.507052], [0.269308, 0.218818, 0.509577], [0.267968, 0.223549, 0.512008], [0.26658, 0.228262, 0.514349], [0.265145, 0.232956, 0.516599], [0.263663, 0.237631, 0.518762], [0.262138, 0.242286, 0.520837], [0.260571, 0.246922, 0.522828], [0.258965, 0.251537, 0.524736], [0.257322, 0.25613, 0.526563], [0.255645, 0.260703, 0.528312], [0.253935, 0.265254, 0.529983], [0.252194, 0.269783, 0.531579], [0.250425, 0.27429, 0.533103], [0.248629, 0.278775, 0.534556], [0.246811, 0.283237, 0.535941], [0.244972, 0.287675, 0.53726], [0.243113, 0.292092, 0.538516], [0.241237, 0.296485, 0.539709], [0.239346, 0.300855, 0.540844], [0.237441, 0.305202, 0.541921], [0.235526, 0.309527, 0.542944], [0.233603, 0.313828, 0.543914], [0.231674, 0.318106, 0.544834], [0.229739, 0.322361, 0.545706], [0.227802, 0.326594, 0.546532], [0.225863, 0.330805, 0.547314], [0.223925, 0.334994, 0.548053], [0.221989, 0.339161, 0.548752], [0.220057, 0.343307, 0.549413], [0.21813, 0.347432, 0.550038], [0.21621, 0.351535, 0.550627], [0.214298, 0.355619, 0.551184], [0.212395, 0.359683, 0.55171], [0.210503, 0.363727, 0.552206], [0.208623, 0.367752, 0.552675], [0.206756, 0.371758, 0.553117], [0.204903, 0.375746, 0.553533], [0.203063, 0.379716, 0.553925], [0.201239, 0.38367, 0.554294], [0.19943, 0.387607, 0.554642], [0.197636, 0.391528, 0.554969], [0.19586, 0.395433, 0.555276], [0.1941, 0.399323, 0.555565], [0.192357, 0.403199, 0.555836], [0.190631, 0.407061, 0.556089], [0.188923, 0.41091, 0.556326], [0.187231, 0.414746, 0.556547], [0.185556, 0.41857, 0.556753], [0.183898, 0.422383, 0.556944], [0.182256, 0.426184, 0.55712], [0.180629, 0.429975, 0.557282], [0.179019, 0.433756, 0.55743], [0.177423, 0.437527, 0.557565], [0.175841, 0.44129, 0.557685], [0.174274, 0.445044, 0.557792], [0.172719, 0.448791, 0.557885], [0.171176, 0.45253, 0.557965], [0.169646, 0.456262, 0.55803], [0.168126, 0.459988, 0.558082], [0.166617, 0.463708, 0.558119], [0.165117, 0.467423, 0.558141], [0.163625, 0.471133, 0.558148], [0.162142, 0.474838, 0.55814], [0.160665, 0.47854, 0.558115], [0.159194, 0.482237, 0.558073], [0.157729, 0.485932, 0.558013], [0.15627, 0.489624, 0.557936], [0.154815, 0.493313, 0.55784], [0.153364, 0.497, 0.557724], [0.151918, 0.500685, 0.557587], [0.150476, 0.504369, 0.55743], [0.149039, 0.508051, 0.55725], [0.147607, 0.511733, 0.557049], [0.14618, 0.515413, 0.556823], [0.144759, 0.519093, 0.556572], [0.143343, 0.522773, 0.556295], [0.141935, 0.526453, 0.555991], [0.140536, 0.530132, 0.555659], [0.139147, 0.533812, 0.555298], [0.13777, 0.537492, 0.554906], [0.136408, 0.541173, 0.554483], [0.135066, 0.544853, 0.554029], [0.133743, 0.548535, 0.553541], [0.132444, 0.552216, 0.553018], [0.131172, 0.555899, 0.552459], [0.129933, 0.559582, 0.551864], [0.128729, 0.563265, 0.551229], [0.127568, 0.566949, 0.550556], [0.126453, 0.570633, 0.549841], [0.125394, 0.574318, 0.549086], [0.124395, 0.578002, 0.548287], [0.123463, 0.581687, 0.547445], [0.122606, 0.585371, 0.546557], [0.121831, 0.589055, 0.545623], [0.121148, 0.592739, 0.544641], [0.120565, 0.596422, 0.543611], [0.120092, 0.600104, 0.54253], [0.119738, 0.603785, 0.5414], [0.119512, 0.607464, 0.540218], [0.119423, 0.611141, 0.538982], [0.119483, 0.614817, 0.537692], [0.119699, 0.61849, 0.536347], [0.120081, 0.622161, 0.534946], [0.120638, 0.625828, 0.533488], [0.12138, 0.629492, 0.531973], [0.122312, 0.633153, 0.530398], [0.123444, 0.636809, 0.528763], [0.12478, 0.640461, 0.527068], [0.126326, 0.644107, 0.525311], [0.128087, 0.647749, 0.523491], [0.130067, 0.651384, 0.521608], [0.132268, 0.655014, 0.519661], [0.134692, 0.658636, 0.517649], [0.137339, 0.662252, 0.515571], [0.14021, 0.665859, 0.513427], [0.143303, 0.669459, 0.511215], [0.146616, 0.67305, 0.508936], [0.150148, 0.676631, 0.506589], [0.153894, 0.680203, 0.504172], [0.157851, 0.683765, 0.501686], [0.162016, 0.687316, 0.499129], [0.166383, 0.690856, 0.496502], [0.170948, 0.694384, 0.493803], [0.175707, 0.6979, 0.491033], [0.180653, 0.701402, 0.488189], [0.185783, 0.704891, 0.485273], [0.19109, 0.708366, 0.482284], [0.196571, 0.711827, 0.479221], [0.202219, 0.715272, 0.476084], [0.20803, 0.718701, 0.472873], [0.214, 0.722114, 0.469588], [0.220124, 0.725509, 0.466226], [0.226397, 0.728888, 0.462789], [0.232815, 0.732247, 0.459277], [0.239374, 0.735588, 0.455688], [0.24607, 0.73891, 0.452024], [0.252899, 0.742211, 0.448284], [0.259857, 0.745492, 0.444467], [0.266941, 0.748751, 0.440573], [0.274149, 0.751988, 0.436601], [0.281477, 0.755203, 0.432552], [0.288921, 0.758394, 0.428426], [0.296479, 0.761561, 0.424223], [0.304148, 0.764704, 0.419943], [0.311925, 0.767822, 0.415586], [0.319809, 0.770914, 0.411152], [0.327796, 0.77398, 0.40664], [0.335885, 0.777018, 0.402049], [0.344074, 0.780029, 0.397381], [0.35236, 0.783011, 0.392636], [0.360741, 0.785964, 0.387814], [0.369214, 0.788888, 0.382914], [0.377779, 0.791781, 0.377939], [0.386433, 0.794644, 0.372886], [0.395174, 0.797475, 0.367757], [0.404001, 0.800275, 0.362552], [0.412913, 0.803041, 0.357269], [0.421908, 0.805774, 0.35191], [0.430983, 0.808473, 0.346476], [0.440137, 0.811138, 0.340967], [0.449368, 0.813768, 0.335384], [0.458674, 0.816363, 0.329727], [0.468053, 0.818921, 0.323998], [0.477504, 0.821444, 0.318195], [0.487026, 0.823929, 0.312321], [0.496615, 0.826376, 0.306377], [0.506271, 0.828786, 0.300362], [0.515992, 0.831158, 0.294279], [0.525776, 0.833491, 0.288127], [0.535621, 0.835785, 0.281908], [0.545524, 0.838039, 0.275626], [0.555484, 0.840254, 0.269281], [0.565498, 0.84243, 0.262877], [0.575563, 0.844566, 0.256415], [0.585678, 0.846661, 0.249897], [0.595839, 0.848717, 0.243329], [0.606045, 0.850733, 0.236712], [0.616293, 0.852709, 0.230052], [0.626579, 0.854645, 0.223353], [0.636902, 0.856542, 0.21662], [0.647257, 0.8584, 0.209861], [0.657642, 0.860219, 0.203082], [0.668054, 0.861999, 0.196293], [0.678489, 0.863742, 0.189503], [0.688944, 0.865448, 0.182725], [0.699415, 0.867117, 0.175971], [0.709898, 0.868751, 0.169257], [0.720391, 0.87035, 0.162603], [0.730889, 0.871916, 0.156029], [0.741388, 0.873449, 0.149561], [0.751884, 0.874951, 0.143228], [0.762373, 0.876424, 0.137064], [0.772852, 0.877868, 0.131109], [0.783315, 0.879285, 0.125405], [0.79376, 0.880678, 0.120005], [0.804182, 0.882046, 0.114965], [0.814576, 0.883393, 0.110347], [0.82494, 0.88472, 0.106217], [0.83527, 0.886029, 0.102646], [0.845561, 0.887322, 0.099702], [0.85581, 0.888601, 0.097452], [0.866013, 0.889868, 0.095953], [0.876168, 0.891125, 0.09525], [0.886271, 0.892374, 0.095374], [0.89632, 0.893616, 0.096335], [0.906311, 0.894855, 0.098125], [0.916242, 0.896091, 0.100717], [0.926106, 0.89733, 0.104071], [0.935904, 0.89857, 0.108131], [0.945636, 0.899815, 0.112838], [0.9553, 0.901065, 0.118128], [0.964894, 0.902323, 0.123941], [0.974417, 0.90359, 0.130215], [0.983868, 0.904867, 0.136897], [0.993248, 0.906157, 0.143936] ]; export function red(value: number): number { return colorTable[getIndex(value)][0]; } export function green(value: number): number { return colorTable[getIndex(value)][1]; } export function blue(value: number): number { return colorTable[getIndex(value)][2]; } function getIndex(value: number): number { return Math.round(value * (colorTable.length - 1)); } } namespace Data { export const cars = { data: [ { Horsepower: 130.0, Origin: 'USA', Miles_per_Gallon: 18.0, Name: 'chevrolet chevelle malibu', index: 0, Acceleration: 12.0, Year: '1970-01-01', Weight_in_lbs: 3504, Cylinders: 8, Displacement: 307.0 }, { Horsepower: 165.0, Origin: 'USA', Miles_per_Gallon: 15.0, Name: 'buick skylark 320', index: 1, Acceleration: 11.5, Year: '1970-01-01', Weight_in_lbs: 3693, Cylinders: 8, Displacement: 350.0 }, { Horsepower: 150.0, Origin: 'USA', Miles_per_Gallon: 18.0, Name: 'plymouth satellite', index: 2, Acceleration: 11.0, Year: '1970-01-01', Weight_in_lbs: 3436, Cylinders: 8, Displacement: 318.0 }, { Horsepower: 150.0, Origin: 'USA', Miles_per_Gallon: 16.0, Name: 'amc rebel sst', index: 3, Acceleration: 12.0, Year: '1970-01-01', Weight_in_lbs: 3433, Cylinders: 8, Displacement: 304.0 }, { Horsepower: 140.0, Origin: 'USA', Miles_per_Gallon: 17.0, Name: 'ford torino', index: 4, Acceleration: 10.5, Year: '1970-01-01', Weight_in_lbs: 3449, Cylinders: 8, Displacement: 302.0 }, { Horsepower: 198.0, Origin: 'USA', Miles_per_Gallon: 15.0, Name: 'ford galaxie 500', index: 5, Acceleration: 10.0, Year: '1970-01-01', Weight_in_lbs: 4341, Cylinders: 8, Displacement: 429.0 }, { Horsepower: 220.0, Origin: 'USA', Miles_per_Gallon: 14.0, Name: 'chevrolet impala', index: 6, Acceleration: 9.0, Year: '1970-01-01', Weight_in_lbs: 4354, Cylinders: 8, Displacement: 454.0 }, { Horsepower: 215.0, Origin: 'USA', Miles_per_Gallon: 14.0, Name: 'plymouth fury iii', index: 7, Acceleration: 8.5, Year: '1970-01-01', Weight_in_lbs: 4312, Cylinders: 8, Displacement: 440.0 }, { Horsepower: 225.0, Origin: 'USA', Miles_per_Gallon: 14.0, Name: 'pontiac catalina', index: 8, Acceleration: 10.0, Year: '1970-01-01', Weight_in_lbs: 4425, Cylinders: 8, Displacement: 455.0 }, { Horsepower: 190.0, Origin: 'USA', Miles_per_Gallon: 15.0, Name: 'amc ambassador dpl', index: 9, Acceleration: 8.5, Year: '1970-01-01', Weight_in_lbs: 3850, Cylinders: 8, Displacement: 390.0 }, { Horsepower: 115.0, Origin: 'Europe', Miles_per_Gallon: null, Name: 'citroen ds-21 pallas', index: 10, Acceleration: 17.5, Year: '1970-01-01', Weight_in_lbs: 3090, Cylinders: 4, Displacement: 133.0 }, { Horsepower: 165.0, Origin: 'USA', Miles_per_Gallon: null, Name: 'chevrolet chevelle concours (sw)', index: 11, Acceleration: 11.5, Year: '1970-01-01', Weight_in_lbs: 4142, Cylinders: 8, Displacement: 350.0 }, { Horsepower: 153.0, Origin: 'USA', Miles_per_Gallon: null, Name: 'ford torino (sw)', index: 12, Acceleration: 11.0, Year: '1970-01-01', Weight_in_lbs: 4034, Cylinders: 8, Displacement: 351.0 }, { Horsepower: 175.0, Origin: 'USA', Miles_per_Gallon: null, Name: 'plymouth satellite (sw)', index: 13, Acceleration: 10.5, Year: '1970-01-01', Weight_in_lbs: 4166, Cylinders: 8, Displacement: 383.0 }, { Horsepower: 175.0, Origin: 'USA', Miles_per_Gallon: null, Name: 'amc rebel sst (sw)', index: 14, Acceleration: 11.0, Year: '1970-01-01', Weight_in_lbs: 3850, Cylinders: 8, Displacement: 360.0 }, { Horsepower: 170.0, Origin: 'USA', Miles_per_Gallon: 15.0, Name: 'dodge challenger se', index: 15, Acceleration: 10.0, Year: '1970-01-01', Weight_in_lbs: 3563, Cylinders: 8, Displacement: 383.0 }, { Horsepower: 160.0, Origin: 'USA', Miles_per_Gallon: 14.0, Name: "plymouth 'cuda 340", index: 16, Acceleration: 8.0, Year: '1970-01-01', Weight_in_lbs: 3609, Cylinders: 8, Displacement: 340.0 }, { Horsepower: 140.0, Origin: 'USA', Miles_per_Gallon: null, Name: 'ford mustang boss 302', index: 17, Acceleration: 8.0, Year: '1970-01-01', Weight_in_lbs: 3353, Cylinders: 8, Displacement: 302.0 }, { Horsepower: 150.0, Origin: 'USA', Miles_per_Gallon: 15.0, Name: 'chevrolet monte carlo', index: 18, Acceleration: 9.5, Year: '1970-01-01', Weight_in_lbs: 3761, Cylinders: 8, Displacement: 400.0 }, { Horsepower: 225.0, Origin: 'USA', Miles_per_Gallon: 14.0, Name: 'buick estate wagon (sw)', index: 19, Acceleration: 10.0, Year: '1970-01-01', Weight_in_lbs: 3086, Cylinders: 8, Displacement: 455.0 }, { Horsepower: 95.0, Origin: 'Japan', Miles_per_Gallon: 24.0, Name: 'toyota corona mark ii', index: 20, Acceleration: 15.0, Year: '1970-01-01', Weight_in_lbs: 2372, Cylinders: 4, Displacement: 113.0 }, { Horsepower: 95.0, Origin: 'USA', Miles_per_Gallon: 22.0, Name: 'plymouth duster', index: 21, Acceleration: 15.5, Year: '1970-01-01', Weight_in_lbs: 2833, Cylinders: 6, Displacement: 198.0 }, { Horsepower: 97.0, Origin: 'USA', Miles_per_Gallon: 18.0, Name: 'amc hornet', index: 22, Acceleration: 15.5, Year: '1970-01-01', Weight_in_lbs: 2774, Cylinders: 6, Displacement: 199.0 }, { Horsepower: 85.0, Origin: 'USA', Miles_per_Gallon: 21.0, Name: 'ford maverick', index: 23, Acceleration: 16.0, Year: '1970-01-01', Weight_in_lbs: 2587, Cylinders: 6, Displacement: 200.0 }, { Horsepower: 88.0, Origin: 'Japan', Miles_per_Gallon: 27.0, Name: 'datsun pl510', index: 24, Acceleration: 14.5, Year: '1970-01-01', Weight_in_lbs: 2130, Cylinders: 4, Displacement: 97.0 }, { Horsepower: 46.0, Origin: 'Europe', Miles_per_Gallon: 26.0, Name: 'volkswagen 1131 deluxe sedan', index: 25, Acceleration: 20.5, Year: '1970-01-01', Weight_in_lbs: 1835, Cylinders: 4, Displacement: 97.0 }, { Horsepower: 87.0, Origin: 'Europe', Miles_per_Gallon: 25.0, Name: 'peugeot 504', index: 26, Acceleration: 17.5, Year: '1970-01-01', Weight_in_lbs: 2672, Cylinders: 4, Displacement: 110.0 }, { Horsepower: 90.0, Origin: 'Europe', Miles_per_Gallon: 24.0, Name: 'audi 100 ls', index: 27, Acceleration: 14.5, Year: '1970-01-01', Weight_in_lbs: 2430, Cylinders: 4, Displacement: 107.0 }, { Horsepower: 95.0, Origin: 'Europe', Miles_per_Gallon: 25.0, Name: 'saab 99e', index: 28, Acceleration: 17.5, Year: '1970-01-01', Weight_in_lbs: 2375, Cylinders: 4, Displacement: 104.0 }, { Horsepower: 113.0, Origin: 'Europe', Miles_per_Gallon: 26.0, Name: 'bmw 2002', index: 29, Acceleration: 12.5, Year: '1970-01-01', Weight_in_lbs: 2234, Cylinders: 4, Displacement: 121.0 } ], schema: { primaryKey: ['index'], fields: [ { name: 'index', type: 'integer' }, { name: 'Acceleration', type: 'number' }, { name: 'Cylinders', type: 'integer' }, { name: 'Displacement', type: 'number' }, { name: 'Horsepower', type: 'number' }, { name: 'Miles_per_Gallon', type: 'number' }, { name: 'Name', type: 'string' }, { name: 'Origin', type: 'string' }, { name: 'Weight_in_lbs', type: 'integer' }, { name: 'Year', type: 'string' } ], pandas_version: '0.20.0' } }; export const editable_test_data = { data: [ { index: 0, link: ['https://www.chevrolet.com/', 'Chevrolet'], Name: 'Chevrolet', Contact: 'info@chevrolet.com', Origin: 'USA', Revenue: '$20-100 bn', Cylinders: [4, 8, 16], Horsepower: 130.0, Models: 2, Automatic: true, 'Date in Service': '1980-01-02', 'Corp. Data': { headquarter: 'USA', num_employees: 100, locations: 80 } }, { index: 1, link: ['https://www.bmw.com/', 'BMW'], Name: 'BMW', Contact: 'info@bmw.com', Origin: 'Germany', Revenue: '$20-100 bn', Cylinders: [3, 6, 8, 16], Horsepower: 120.0, Models: 3, Automatic: true, 'Date in Service': '1990-11-22', 'Corp. Data': { headquarter: 'Germany', num_employees: 200, locations: 20 } }, { index: 2, link: ['https://www.mercedes-benz.com/', 'Mercedes'], Name: 'Mercedes', Contact: 'info@mbusa.com', Origin: 'Germany', Revenue: '$20-100 bn', Cylinders: [4, 8, 16], Horsepower: 100.0, Models: 5, Automatic: false, 'Date in Service': '1970-06-13', 'Corp. Data': { headquarter: 'Germany', num_employees: 250, locations: 45 } }, { index: 3, link: ['https://www.honda.com/', 'Honda'], Name: 'Honda', Contact: 'info@honda.com', Origin: 'Japan', Revenue: '$5-20 bn', Cylinders: [4], Horsepower: 90.0, Models: 5, Automatic: true, 'Date in Service': '1985-05-09', 'Corp. Data': { headquarter: 'Germany', num_employees: 200, locations: 40 } }, { index: 4, link: ['https://www.toyota.com/', 'Toyota'], Name: 'Toyota', Contact: 'info@toyota.com', Origin: 'Japan', Revenue: '$20-100 bn', Cylinders: [2, 3, 4, 6, 8, 16], Horsepower: 95.0, Models: 7, Automatic: true, 'Date in Service': '1975-05-19', 'Corp. Data': { headquarter: 'Japan', num_employees: 500, locations: 70 } }, { index: 5, link: ['https://www.renaultgroup.com/', 'Renault'], Name: 'Renault', Contact: 'info@renault.com', Origin: 'France', Revenue: '$1-5 bn', Cylinders: [2, 3, 4], Horsepower: 75.0, Models: 4, Automatic: false, 'Date in Service': '1962-07-28', 'Corp. Data': { headquarter: 'France', num_employees: 400, locations: 80 } } ], schema: { primaryKey: ['index'], fields: [ { name: 'index', type: 'integer' }, { name: 'link', type: 'object' }, { name: 'Name', type: 'string', constraint: { minLength: 2, maxLength: 100, pattern: '[a-zA-Z]' } }, { name: 'Origin', type: 'string', constraint: { enum: 'dynamic' } }, { name: 'Revenue', type: 'string', constraint: { enum: ['$1-5 bn', '$5-20 bn', '$20-100 bn'] } }, { name: 'Cylinders', type: 'array', constraint: { enum: [2, 3, 4, 6, 8, 16] } }, { name: 'Horsepower', type: 'number', constraint: { minimum: 50, maximum: 900 } }, { name: 'Models', type: 'integer', constraint: { minimum: 1, maximum: 30 } }, { name: 'Automatic', type: 'boolean' }, { name: 'Date in Service', type: 'date' }, { name: 'Contact', type: 'string', format: 'email' }, { name: 'Corp. Data', type: 'object' } ], pandas_version: '0.20.0' } }; } lumino-2021.12.13/examples/example-datagrid/style/000077500000000000000000000000001415564225700216075ustar00rootroot00000000000000lumino-2021.12.13/examples/example-datagrid/style/index.css000066400000000000000000000013761415564225700234370ustar00rootroot00000000000000/*----------------------------------------------------------------------------- | Copyright (c) 2014-2018, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ @import '~@lumino/default-theme/style/index.css'; body { display: flex; flex-direction: column; position: absolute; top: 0; left: 0; right: 0; bottom: 0; margin: 0; padding: 0; overflow: hidden; } #dock { flex: 1 1 auto; padding: 4px; } .content-wrapper { padding: 8px; border: 1px solid #c0c0c0; border-top: none; background: white; box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.2); } lumino-2021.12.13/examples/example-datagrid/tsconfig.json000066400000000000000000000005711415564225700231610ustar00rootroot00000000000000{ "compilerOptions": { "declaration": false, "noImplicitAny": true, "noEmitOnError": true, "noUnusedLocals": true, "strictNullChecks": true, "module": "commonjs", "moduleResolution": "node", "target": "ES5", "outDir": "./build", "lib": ["ES5", "ES2015.Promise", "ES2015.Iterable", "DOM"], "types": [] }, "include": ["src/*"] } lumino-2021.12.13/examples/example-datagrid/webpack.config.js000066400000000000000000000005461415564225700236720ustar00rootroot00000000000000var path = require('path'); module.exports = { entry: './build/index.js', mode: 'development', output: { path: __dirname + '/build/', filename: 'bundle.example.js', publicPath: './build/' }, module: { rules: [ { test: /\.css$/, use: ['style-loader', 'css-loader'] }, { test: /\.png$/, use: 'file-loader' } ] } }; lumino-2021.12.13/examples/example-datastore/000077500000000000000000000000001415564225700206565ustar00rootroot00000000000000lumino-2021.12.13/examples/example-datastore/index.html000066400000000000000000000002401415564225700226470ustar00rootroot00000000000000 lumino-2021.12.13/examples/example-datastore/package.json000066400000000000000000000015251415564225700231470ustar00rootroot00000000000000{ "name": "@lumino/example-datastore", "version": "0.19.1", "private": true, "scripts": { "build": "tsc && webpack", "clean": "rimraf build", "start": "node build/server.js" }, "dependencies": { "@lumino/algorithm": "^1.9.1", "@lumino/commands": "^1.19.1", "@lumino/coreutils": "^1.11.1", "@lumino/datastore": "^0.17.1", "@lumino/disposable": "^1.10.1", "@lumino/dragdrop": "^1.13.1", "@lumino/widgets": "^1.30.1", "codemirror": "^5.48.2", "es6-promise": "^4.0.5", "websocket": "^1.0.28" }, "devDependencies": { "@types/codemirror": "^0.0.76", "@types/node": "^10.12.19", "@types/websocket": "^0.0.40", "css-loader": "^3.4.0", "file-loader": "^5.0.2", "rimraf": "^3.0.2", "style-loader": "^1.0.2", "typescript": "~3.6.0", "webpack": "^4.41.3" } } lumino-2021.12.13/examples/example-datastore/src/000077500000000000000000000000001415564225700214455ustar00rootroot00000000000000lumino-2021.12.13/examples/example-datastore/src/collaborator.ts000066400000000000000000000033101415564225700244750ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2019, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { UUID } from '@lumino/coreutils'; // A unique id for this collaborator. export const COLLABORATOR_ID = UUID.uuid4(); // Colors to choose from when rendering collaborator cursors. const COLORS = [ '#e41a1c', '#377eb8', '#4daf4a', '#984ea3', '#ff7f00', '#a65628', '#f781bf', '#999999' ]; // Names to choose from when rendering collaborator cursors, chosen from // Ubuntu release codenames. const NAMES = [ 'Warty Warthog', 'Hoary Hedgehog', 'Breezy Badger', 'Dapper Drake', 'Edgy Eft', 'Feisty Fawn', 'Gutsy Gibbon', 'Hardy Heron', 'Intrepid Ibex', 'Jaunty Jackalope', 'Karmic Koala', 'Lucid Lynx', 'Maverick Meerkat', 'Natty Narwhal', 'Oneiric Ocelot', 'Precise Pangolin', 'Quantal Quetzal', 'Raring Ringtail', 'Saucy Salamander', 'Trusty Tahr', 'Utopic Unicorn', 'Vivid Vervet', 'Wily Werewolf', 'Xenial Xerus', 'Yakkety Yak', 'Zesty Zapus', 'Artful Aardvark', 'Bionic Beaver', 'Cosmic Cuttlefish', 'Disco Dingo', 'Eoan Ermine' ]; // A random color for the current collaborator. export const COLLABORATOR_COLOR = COLORS[Math.floor(Math.random() * COLORS.length)]; // A random name for the current collaborator. export const COLLABORATOR_NAME = NAMES[Math.floor(Math.random() * NAMES.length)]; lumino-2021.12.13/examples/example-datastore/src/index.ts000066400000000000000000000066121415564225700231310ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2019, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { ArrayExt } from '@lumino/algorithm'; import { CommandRegistry } from '@lumino/commands'; import { BoxPanel, DockPanel, Widget } from '@lumino/widgets'; import { WSAdapter } from './wsadapter'; import { CodeMirrorEditor, EDITOR_SCHEMA } from './widget'; import '../style/index.css'; let commands = new CommandRegistry(); /** * Initialize the applicaiton. */ async function init(): Promise { // Create a transaction server adapter, which is responsible for creating // a new datastore as well as managing its connection to the server. let adapter = new WSAdapter( () => new WebSocket(window.location.origin.replace(/^http/, 'ws')) ); // Create the datastore. let store = await adapter.createStore([EDITOR_SCHEMA]); // The datastore may come prepopulated with a transaction history. // If it is empty, that means we are the first collaborator, in // which case we initialize it. let editorTable = store.get(EDITOR_SCHEMA); if (editorTable.isEmpty) { store.beginTransaction(); try { editorTable.update({ e1: {}, e2: {}, e3: {} }); } finally { store.endTransaction(); } } // Set up the text editors. let e1 = new CodeMirrorEditor(store, 'e1'); e1.title.label = 'First Document'; let e2 = new CodeMirrorEditor(store, 'e2'); e2.title.label = 'Second Document'; let e3 = new CodeMirrorEditor(store, 'e3'); e3.title.label = 'Third Document'; // Add the text editors to a dock panel. let dock = new DockPanel({ spacing: 4 }); dock.addWidget(e1); dock.addWidget(e2, { mode: 'split-right', ref: e1 }); dock.addWidget(e3, { mode: 'split-bottom', ref: e2 }); dock.id = 'dock'; // Add the dock panel to the document. let box = new BoxPanel({ spacing: 2 }); box.id = 'main'; box.addWidget(dock); window.onresize = () => { box.update(); }; Widget.attach(box, document.body); // Add commands for undo and redo. commands.addCommand('undo', { label: 'Undo', execute: () => { let editor = ArrayExt.findFirstValue( [e1, e2, e3], (e: CodeMirrorEditor) => e.hasFocus() ); if (editor) { editor.undo(); } } }); commands.addCommand('redo', { label: 'Redo', execute: () => { let editor = ArrayExt.findFirstValue( [e1, e2, e3], (e: CodeMirrorEditor) => e.hasFocus() ); if (editor) { editor.redo(); } } }); // Add keybindings for undo and redo. commands.addKeyBinding({ keys: ['Accel Z'], selector: 'body', command: 'undo' }); commands.addKeyBinding({ keys: ['Accel Shift Z'], selector: 'body', command: 'redo' }); // Add an event listener to the document. Mark it as capturing so that we can // intercept undo/redo handling for the CodeMirror instances. document.addEventListener( 'keydown', (event: KeyboardEvent) => { commands.processKeydownEvent(event); }, true ); } window.onload = init; lumino-2021.12.13/examples/example-datastore/src/messages.ts000066400000000000000000000220041415564225700236220ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2018, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { JSONObject, ReadonlyJSONObject, UUID } from '@lumino/coreutils'; import { Datastore } from '@lumino/datastore'; /** * A patch history object for the datastore. */ export type TransactionHistory = { /** * All known transactions. */ readonly transactions: ReadonlyArray; }; /** * A namespace for messages for communicating with the patch server. */ export namespace WSAdapterMessages { /** * A base message for the patch server. */ export type IBaseMessage = JSONObject & { msgId: string; msgType: | 'storeid-request' | 'storeid-reply' | 'undo-request' | 'undo-reply' | 'redo-request' | 'redo-reply' | 'transaction-broadcast' | 'transaction-ack' | 'history-request' | 'history-reply' | 'fetch-transaction-request' | 'fetch-transaction-reply'; readonly content: ReadonlyJSONObject; }; export type IBaseReplyMessage = IBaseMessage & { parentId: string; }; /** * A message representing a request for a unique store ID from the server. */ export type IStoreIdMessageRequest = IBaseMessage & { msgType: 'storeid-request'; content: {}; }; /** * A reply from the server containing a unique store ID. */ export type IStoreIdMessageReply = IBaseReplyMessage & { msgType: 'storeid-reply'; content: { readonly storeId: number; }; }; /** * A message representing a request to undo a transaction. */ export type IUndoMessageRequest = IBaseMessage & { msgType: 'undo-request'; content: { readonly transactionId: string; }; }; /** * A reply from the server containing a transaction to undo. */ export type IUndoMessageReply = IBaseReplyMessage & { msgType: 'undo-reply'; content: { readonly transaction: Datastore.Transaction; }; }; /** * A message representing a request to redo a transaction. */ export type IRedoMessageRequest = IBaseMessage & { msgType: 'redo-request'; content: { readonly transactionId: string; }; }; /** * A reply from the server containing a transaction to redo. */ export type IRedoMessageReply = IBaseReplyMessage & { msgType: 'redo-reply'; content: { readonly transaction: Datastore.Transaction; }; }; /** * A message from a client broadcasting a transaction. */ export type ITransactionBroadcastMessage = IBaseMessage & { msgType: 'transaction-broadcast'; content: { readonly transactions: ReadonlyArray; }; }; /** * A message from the server acknowledging receipt of a transaction. */ export type ITransactionAckMessage = IBaseReplyMessage & { msgType: 'transaction-ack'; content: { readonly transactionIds: string[]; }; }; /** * A request from a client for the patch history from the server. */ export type IHistoryRequestMessage = IBaseMessage & { msgType: 'history-request'; content: {}; }; /** * A response from the server with the patch history. */ export type IHistoryReplyMessage = IBaseReplyMessage & { msgType: 'history-reply'; content: { history: TransactionHistory; }; }; /** * A base reply message from the server. */ export type IReplyMessage = | IStoreIdMessageReply | ITransactionAckMessage | IUndoMessageReply | IRedoMessageReply | IHistoryReplyMessage; /** * A WSAdapter message. */ export type IMessage = | IStoreIdMessageRequest | ITransactionBroadcastMessage | IUndoMessageRequest | IRedoMessageRequest | IHistoryRequestMessage | IReplyMessage; /** * Create a WSServerAdapter message. * * @param msgType - The message type. * * @param content - The content of the message. * * @param parentId - An optional id of the parent of this message. * * @returns The created message. */ export function createMessage( msgType: T['msgType'], content?: T['content'], parentId?: string ): T { let msgId = UUID.uuid4(); if (content === undefined) { content = {}; } return { msgId, msgType, parentId, content } as T; } /** * Create a WSServerAdapter message. * * @param msgType - The message type. * * @param content - The content of the message. * * @param parentId - The id of the parent of this reply. * * @returns The created message. */ export function createReply( msgType: T['msgType'], content: T['content'], parentId: string ): T { let msgId = UUID.uuid4(); return { msgId, msgType, parentId, content } as T; } /** * Create a `'storeid-request'` message. * * @returns The created message. */ export function createStoreIdRequestMessage(): IStoreIdMessageRequest { return createMessage('storeid-request', {}); } /** * Create a `'storeid-reply'` message. * * @param parentId - The id of the parent of this reply. * * @param storeId - The assigned storeId. * * @returns The created message. */ export function createStoreIdReplyMessage( parentId: string, storeId: number ): IStoreIdMessageReply { return createReply('storeid-reply', { storeId }, parentId); } /** * Create a `'undo-request'` message. * * @returns {IUndoMessageRequest} The created message. */ export function createUndoRequestMessage(id: string): IUndoMessageRequest { return createMessage('undo-request', { transactionId: id }); } /** * Create a `'undo-reply'` message. * * @param parentId - The id of the parent of this reply. * * @param storeId - The assigned storeId. * * @returns The created message. */ export function createUndoReplyMessage( parentId: string, transaction: Datastore.Transaction ): IUndoMessageReply { return createReply( 'undo-reply', { transaction: transaction as any }, parentId ); } /** * Create a `'redo-request'` message. * * @returns The created message. */ export function createRedoRequestMessage(id: string): IRedoMessageRequest { return createMessage('redo-request', { transactionId: id }); } /** * Create a `'undo-reply'` message. * * @param parentId - The id of the parent of this reply. * * @param storeId - The assigned storeId. * * @returns The created message. */ export function createRedoReplyMessage( parentId: string, transaction: Datastore.Transaction ): IRedoMessageReply { return createReply( 'redo-reply', { transaction: transaction as any }, parentId ); } /** * Create a `'transaction-broadcast'` message. * * @param transactions - The transactions of the message. * * @returns The created message. */ export function createTransactionBroadcastMessage( transactions: Datastore.Transaction[] ): ITransactionBroadcastMessage { // TS complains about not being able to cast Transaction[] to JSONArray // so for now cast to any: return createMessage('transaction-broadcast', { transactions: transactions as any }); } /** * Create a `'transaction-ack'` message. * * @param parentId - The id of the parent of this reply. * * @param transactionIds - The ids of the acknowledged transactions. * * @returns The created message. */ export function createTransactionAckMessage( parentId: string, transactionIds: string[] ): ITransactionAckMessage { return createReply('transaction-ack', { transactionIds }, parentId); } /** * Create a `'history-request'` message. * * @returns {IHistoryRequestMessage} The created message. */ export function createHistoryRequestMessage(): IHistoryRequestMessage { return createMessage('history-request', {}); } /** * Create a `'history-reply'` message. * * @param parentId - The id of the parent of this reply. * * @param history - The history of this reply. * * @returns The created message. */ export function createHistoryReplyMessage( parentId: string, history: TransactionHistory ): IHistoryReplyMessage { return createReply('history-reply', { history } as any, parentId); } /** * A type guard for whether a given message is a reply message. * * @param message - the message in question * * @returns whether the message is a reply message. */ export function isReply(message: IMessage): message is IReplyMessage { return message.parentId !== undefined; } } lumino-2021.12.13/examples/example-datastore/src/server.ts000066400000000000000000000222401415564225700233230ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2019, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { Datastore } from '@lumino/datastore'; import { connection, server as WebSocketServer } from 'websocket'; import { WSAdapterMessages } from './messages'; import * as fs from 'fs'; import * as http from 'http'; import * as path from 'path'; /** * Create a HTTP static file server for serving the static * assets to the user. */ let server = http.createServer((request, response) => { console.log('request starting...'); let filePath = '.' + request.url; if (filePath == './') { filePath = './index.html'; } let extname = path.extname(filePath); let contentType = 'text/html'; switch (extname) { case '.js': contentType = 'text/javascript'; break; case '.css': contentType = 'text/css'; break; } fs.readFile(filePath, (error, content) => { if (error) { console.error('Could not find file'); response.writeHead(404, { 'Content-Type': contentType }); response.end(); } else { response.writeHead(200, { 'Content-Type': contentType }); response.end(content, 'utf-8'); } }); }); // Start the server server.listen(8000, () => { console.info(new Date() + ' Page server is listening on port 8000'); }); // Create a websocket server for communication of transactions. let wsServer = new WebSocketServer({ httpServer: server, autoAcceptConnections: false, maxReceivedFrameSize: 10 * 1024 * 1024, maxReceivedMessageSize: 1000 * 1024 * 1024 }); /** * A stub for determining whether websocket connections from a particular * origin should be accepted. */ function originIsAllowed(origin: string) { // put logic here to detect whether the specified origin is allowed. return true; } /** * A patch store for the server. */ class TransactionStore { /** * Construct a new patch store. */ constructor() { this._transactions = {}; this._order = []; } /** * Add a transaction to the patch store. * * @param The transaction to add to the store. * * @returns whether it was successfully added. */ add(transaction: Datastore.Transaction): boolean { if (this._transactions.hasOwnProperty(transaction.id)) { return false; } this._transactions[transaction.id] = transaction; this._order.push(transaction.id); let count = this._cemetery[transaction.id]; if (count === undefined) { this._cemetery[transaction.id] = 1; } else { this._cemetery[transaction.id] = count + 1; } return true; } /** * Get a transaction by id. * * @param transactionId: the id of the transaction. * * @returns the transaction, or undefined if it can't be found. */ get(transactionId: string): Datastore.Transaction | undefined { return this._transactions[transactionId]; } /** * Handle a transaction undo. * * @param transactionId - the ID of the transaction to undo. * * #### Notes * This has no effect on the content of the transaction. Instead, it * updates its undo count in the internal cemetery, determining whether * the transaction should be applied at any given time. */ undo(transactionId: string): void { let count = this._cemetery[transactionId]; if (count === undefined) { this._cemetery[transactionId] = -1; } else { this._cemetery[transactionId] = count - 1; } } /** * Handle a transaction redo. * * @param transactionId - the ID of the transaction to redo. * * #### Notes * This has no effect on the content of the transaction. Instead, it * updates its undo count in the internal cemetery, determining whether * the transaction should be applied at any given time. */ redo(transactionId: string): void { let count = this._cemetery[transactionId]; if (count === undefined) { this._cemetery[transactionId] = 0; } else { this._cemetery[transactionId] = count + 1; } } /** * Get the entire history for the transaction store. * * @returns an array of transactions representing the whole history. */ getHistory(): Datastore.Transaction[] { let history = []; for (let id of this._order) { let count = this._cemetery[id] || 0; if (count > 0) { history.push(this._transactions[id]); } } return history; } private _order: string[]; private _transactions: { [id: string]: Datastore.Transaction }; private _cemetery: { [id: string]: number } = {}; } // Create a transaction store. let store = new TransactionStore(); let idCounter = 0; // Keep a running list of current connections. let connections: { [store: number]: connection } = {}; // Lifecycle for a collaborator. wsServer.on('request', request => { if (!originIsAllowed(request.origin)) { // Make sure we only accept requests from an allowed origin request.reject(); console.info( new Date() + ' Connection from origin ' + request.origin + ' rejected.' ); return; } let connection = request.accept(undefined, request.origin); console.debug(new Date() + ' Connection accepted.'); // Handle a message from a collaborator. connection.on('message', message => { // Only allow UTF-8 encoded messages. if (message.type !== 'utf8') { console.debug('Received non-UTF8 Message: ' + message); return; } let data = JSON.parse(message.utf8Data!) as WSAdapterMessages.IMessage; console.debug(`Received message of type: ${data.msgType}`); let reply: WSAdapterMessages.IReplyMessage; let transaction: Datastore.Transaction | undefined; switch (data.msgType) { case 'storeid-request': connections[idCounter] = connection; reply = WSAdapterMessages.createStoreIdReplyMessage( data.msgId, idCounter++ ); break; case 'transaction-broadcast': let acks = []; let propagate = []; for (let t of data.content.transactions) { if (store.add(t)) { acks.push(t.id); propagate.push(t); } } let propMsgStr = JSON.stringify( WSAdapterMessages.createTransactionBroadcastMessage(propagate) ); // Broadcast the transaction to all the other collaborators for (let storeId in connections) { let c = connections[storeId]; if (c !== connection) { console.debug(`Broadcasting transactions to: ${storeId}`); c.sendUTF(propMsgStr); } } reply = WSAdapterMessages.createTransactionAckMessage(data.msgId, acks); break; case 'undo-request': transaction = store.get(data.content.transactionId); if (!transaction) { return; } // Mark the transaction as undone in the datastore, which decrements // its count in the cemetery. store.undo(transaction.id); reply = WSAdapterMessages.createUndoReplyMessage( data.msgId, transaction ); // Broadcast the undo reply to all the other collaborators for (let storeId in connections) { let c = connections[storeId]; if (c !== connection) { console.debug(`Broadcasting undo to: ${storeId}`); c.sendUTF(JSON.stringify(reply)); } } break; case 'redo-request': transaction = store.get(data.content.transactionId); if (!transaction) { return; } // Mark the transaction as redone in the datastore, which increments // its count in the cemetery. store.redo(transaction.id); reply = WSAdapterMessages.createRedoReplyMessage( data.msgId, transaction ); // Broadcast the undo reply to all the other collaborators for (let storeId in connections) { let c = connections[storeId]; if (c !== connection) { console.debug(`Broadcasting redo to: ${storeId}`); c.sendUTF(JSON.stringify(reply)); } } break; case 'history-request': let history = store.getHistory(); reply = WSAdapterMessages.createHistoryReplyMessage(data.msgId, { transactions: history }); break; default: return; } console.debug(`Sending reply: ${reply.msgType}`); connection.sendUTF(JSON.stringify(reply)); }); // Handle a close event from a collaborator. connection.on('close', (reasonCode, description) => { let storeId; for (let id in connections) { if (connections[id] === connection) { storeId = id; } } console.debug( new Date() + ` Store ID ${storeId} disconnected. Reason: ${reasonCode}: ${description}` ); for (let id in connections) { if (connections[id] === connection) { delete connections[id]; break; } } }); }); lumino-2021.12.13/examples/example-datastore/src/widget.ts000066400000000000000000000414641415564225700233110ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2019, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { JSONExt, JSONObject } from '@lumino/coreutils'; import { Datastore, Fields, RegisterField, TextField } from '@lumino/datastore'; import { DisposableDelegate, IDisposable } from '@lumino/disposable'; import { Panel, Widget } from '@lumino/widgets'; import * as CodeMirror from 'codemirror'; import { COLLABORATOR_COLOR, COLLABORATOR_ID, COLLABORATOR_NAME } from './collaborator'; /** * The time that a collaborator name hover persists. */ const HOVER_TIMEOUT = 1000; /** * A schema for an editor model. Currently contains two fields, the current * value and whether the model is read only. Other fields could include * language, mimetype, and collaborator cursors. */ export const EDITOR_SCHEMA = { id: 'editor', fields: { readOnly: Fields.Boolean(), text: Fields.Text(), collaborators: Fields.Map() } }; /** * An interface representing a location in an editor. */ export interface IPosition extends JSONObject { /** * The cursor line number. */ readonly line: number; /** * The cursor column number. */ readonly column: number; } /** * An interface representing a user selection. */ export interface ITextSelection extends JSONObject { /** * The start of the selection. */ readonly start: IPosition; /** * The end of the selection. */ readonly end: IPosition; } /** * An interface representing collaborator cursor state. */ export interface ICollaboratorState extends JSONObject { /** * The current cursor selections. */ selections: ITextSelection[]; /** * A display name for the collaborator. */ name: string; /** * A display color for the collaborator. */ color: string; } /** * A widget which hosts a collaborative CodeMirror text editor. */ export class CodeMirrorEditor extends Panel { /** * Create a new editor widget. * * @param datastore: a datastore which holds an editor table using `EDITOR_SCHEMA`. * * @param record: the record to watch in the editor table. */ constructor(datastore: Datastore, record: string) { super(); this.addClass('content'); this._store = datastore; this._record = record; // Get initial values for the editor let editorTable = this._store.get(EDITOR_SCHEMA); let initialValue = editorTable.get(record)!.text; let readOnly = editorTable.get(record)!.readOnly; // Set up the DOM structure this._editorWidget = new Widget(); this._checkWidget = new Widget(); this._checkWidget.node.style.height = `${this._toolbarHeight}px`; this._checkWidget.addClass('read-only-check'); this._checkWidget.node.textContent = 'Read Only'; this._check = document.createElement('input'); this._check.type = 'checkbox'; this._check.checked = readOnly; this._checkWidget.node.appendChild(this._check); this.addWidget(this._checkWidget); this.addWidget(this._editorWidget); // Listen to changes in the checkbox. this._check.onchange = this._onChecked.bind(this); // Create the editor instance. this._editor = CodeMirror(this._editorWidget.node, { value: initialValue, lineNumbers: true, readOnly }); // Listen for changes on the editor model. CodeMirror.on( this._editor.getDoc(), 'beforeChange', this._onEditorChange.bind(this) ); // Listen for changes to the editor cursors. CodeMirror.on( this._editor, 'cursorActivity', this._onCursorActivity.bind(this) ); // Listen for changes on the datastore. datastore.changed.connect(this._onDatastoreChange, this); } /** * Undo the last user action. */ undo(): void { let id = this._undo.pop(); if (id) { this._redo.push(id); void this._store.undo(id); } } /** * Redo the last user action. */ redo(): void { let id = this._redo.pop(); if (id) { this._undo.push(id); void this._store.redo(id); } } /** * Whether the editor is currently focused. */ hasFocus(): boolean { return this.node.contains(document.activeElement); } /** * Handle the DOM events for the editor. * * @param event - The DOM event sent to the editor. * * #### Notes * This method implements the DOM `EventListener` interface and is * called in response to events on the editor's DOM node. It should * not be called directly by user code. */ handleEvent(event: Event): void { switch (event.type) { case 'scroll': this._clearHover(); break; default: break; } } /** * A message handler invoked on a `'resize'` message. * * @param msg - the resize message. */ protected onResize(msg: Widget.ResizeMessage): void { if (msg.width >= 0 && msg.height >= 0) { this._editor.setSize(msg.width, msg.height - this._toolbarHeight); } else if (this.isVisible) { this._editor.setSize(null, null); } this._editor.refresh(); this._clearHover(); } /** * Handle a `after-show` message for the editor. */ protected onAfterShow() { this._editor.refresh(); } /** * Handle a `after-attach` message for the editor. */ protected onAfterAttach() { this.node.addEventListener('scroll', this, true); } /** * Handle a `before-detach` message for the editor. */ protected onBeforeDetach() { this.node.removeEventListener('scroll', this, true); } /** * Handle a local check event. */ private _onChecked(): void { // If this was a remote change, we are done. if (this._changeGuard) { return; } // Update the readonly state this._editor.setOption('readOnly', this._check.checked); // Update the table to broadcast the change. let editorTable = this._store.get(EDITOR_SCHEMA); let id = this._store.beginTransaction(); editorTable.update({ [this._record]: { readOnly: this._check.checked } }); this._store.endTransaction(); // Update the undo/redo stack. this._undo.push(id); this._redo.length = 0; } /** * Handle a local editor change. * * @param doc - the CodeMirror doc which changed. * * @param change - the CodeMirror change payload. */ private _onEditorChange( doc: CodeMirror.Doc, change: CodeMirror.EditorChange ): void { // If this was a remote change, we are done. if (this._changeGuard) { return; } let editorTable = this._store.get(EDITOR_SCHEMA); let start = doc.indexFromPos(change.from); let end = doc.indexFromPos(change.to); let text = change.text.join('\n'); // If this was a local change, update the table. let id = this._store.beginTransaction(); editorTable.update({ [this._record]: { text: { index: start, remove: end - start, text: text } } }); this._store.endTransaction(); // Update the undo/redo stack. this._undo.push(id); this._redo.length = 0; } /** * Respond to a change in the editor cursors. */ private _onCursorActivity(): void { // Only add selections inf the editor has focus. This avoids unwanted // triggering of cursor activity due to other collaborator actions. if (this._editor.hasFocus()) { let selections = Private.getSelections(this._editor.getDoc()); let name = COLLABORATOR_NAME; let color = COLLABORATOR_COLOR; let editorTable = this._store.get(EDITOR_SCHEMA); this._store.beginTransaction(); editorTable.update({ [this._record]: { collaborators: { [COLLABORATOR_ID]: { name, color, selections } } } }); this._store.endTransaction(); } } /** * Respond to a change on the datastore. * * @param store - the datastore which changed. * * @param change - the change content. */ private _onDatastoreChange( store: Datastore, change: Datastore.IChangedArgs ): void { // Ignore changes that have already been applied locally. if (change.type === 'transaction' && change.storeId === store.id) { return; } let doc = this._editor.getDoc(); // Apply text field changes to the editor. let c = change.change['editor']; if (c && c[this._record] && c[this._record].text) { let textChanges = c[this._record].text as TextField.Change; textChanges.forEach(tc => { // Convert the change data to codemirror range and inserted text. let from = doc.posFromIndex(tc.index); let to = doc.posFromIndex(tc.index + tc.removed.length); let replacement = tc.inserted; // Apply the operation, setting the change guard so we can ignore // the change signals from codemirror. this._changeGuard = true; doc.replaceRange(replacement, from, to, '+input'); this._changeGuard = false; }); } // If the readonly state has changed, update the check box, setting the // change guard so we can ignore it in the onchange event. if (c && c[this._record] && c[this._record].readOnly) { this._changeGuard = true; let checkChange = c[this._record].readOnly as RegisterField.Change< boolean >; this._check.checked = checkChange.current; // Update the readonly state this._editor.setOption('readOnly', this._check.checked); this._changeGuard = false; } // If the collaborator state has changed, rerender any selections. if (c && c[this._record] && c[this._record].collaborators) { let record = store.get(EDITOR_SCHEMA).get(this._record)!; let { collaborators } = record; this._clearAllSelections(); let ids = Object.keys(collaborators); for (let id of ids) { if (id !== COLLABORATOR_ID) { this._markSelections(id, collaborators[id]!); } } } } /** * Clean currently shown selections for the editor. */ private _clearAllSelections() { this._clearHover(); let ids = Object.keys(this._selectionMarkers); for (let id of ids) { this._clearSelections(id); } } /** * Clean currently shown selections for a given collaborator. */ private _clearSelections(id: string) { let ids = Object.keys(this._selectionMarkers); if (this._hoverId === id) { this._clearHover(); } for (let id of ids) { let markers = this._selectionMarkers[id]!; markers.forEach(marker => { marker.clear(); }); delete this._selectionMarkers[id]; } } /** * Marks selections. */ private _markSelections(uuid: string, collaborator: ICollaboratorState) { let markers: CodeMirror.TextMarker[] = []; let doc = this._editor.getDoc(); // Style each selection for the uuid. collaborator.selections.forEach(selection => { // Only render selections if the start is not equal to the end. // In that case, we don't need to render the cursor. if (!JSONExt.deepEqual(selection.start, selection.end)) { // Selections only appear to render correctly if the anchor // is before the head in the document. That is, reverse selections // do not appear as intended. let forward: boolean = selection.start.line < selection.end.line || (selection.start.line === selection.end.line && selection.start.column <= selection.end.column); let anchor = Private.toCodeMirrorPosition( forward ? selection.start : selection.end ); let head = Private.toCodeMirrorPosition( forward ? selection.end : selection.start ); let markerOptions = Private.toTextMarkerOptions(collaborator); markers.push(doc.markText(anchor, head, markerOptions)); } else { let caret = this._getCaret(uuid, collaborator); markers.push( doc.setBookmark(Private.toCodeMirrorPosition(selection.end), { widget: caret }) ); } }); this._selectionMarkers[uuid] = markers; // Set a timer to auto-dispose of these markers, cleaning up after // collaborators who might have left. We choose a random time between one // and two minutes to avoid racing between active collaborators. if (this._timers[uuid]) { window.clearTimeout(this._timers[uuid]); } this._timers[uuid] = window.setTimeout(() => { this._clearSelections(uuid); delete this._timers[uuid]; }, 60 * 1000 * (1 + Math.random())); } /** * Construct a caret element representing the position * of a collaborator's cursor. */ private _getCaret( uuid: string, collaborator: ICollaboratorState ): HTMLElement { let { name, color } = collaborator; let hoverTimeout: number; let caret: HTMLElement = document.createElement('span'); caret.className = 'collaborator-cursor'; caret.style.borderBottomColor = color; caret.onmouseenter = () => { this._clearHover(); let rect = caret.getBoundingClientRect(); // Construct and place the hover box. let hover = document.createElement('div'); hover.className = 'collaborator-cursor-hover'; hover.style.left = String(rect.left) + 'px'; hover.style.top = String(rect.bottom) + 'px'; hover.textContent = name; hover.style.backgroundColor = color; // If the user mouses over the hover, take over the timer. hover.onmouseenter = () => { window.clearTimeout(hoverTimeout); }; hover.onmouseleave = () => { hoverTimeout = window.setTimeout(() => { this._clearHover(); }, HOVER_TIMEOUT); }; document.body.appendChild(hover); this._hoverId = uuid; this._hover = new DisposableDelegate(() => { window.clearTimeout(hoverTimeout); document.body.removeChild(hover); }); }; caret.onmouseleave = () => { hoverTimeout = window.setTimeout(() => { this._clearHover(); }, HOVER_TIMEOUT); }; return caret; } /** * If there is currently a hover box of a collaborator name, clear it. */ private _clearHover(): void { this._hoverId = ''; if (this._hover) { this._hover.dispose(); this._hover = null; } } /** * Converts an editor selection to a code mirror selection. */ private _changeGuard: boolean = false; private _check: HTMLInputElement; private _checkWidget: Widget; private _editor: CodeMirror.Editor; private _editorWidget: Widget; private _hover: IDisposable | null = null; private _hoverId: string = ''; private _record: string; private _selectionMarkers: { [key: string]: CodeMirror.TextMarker[] | undefined; } = {}; private _store: Datastore; private _timers: { [key: string]: number } = {}; private _toolbarHeight = 24; private _undo: string[] = []; private _redo: string[] = []; } /** * A namespace for module-private functionality. */ namespace Private { /** * Create CodeMirror text marker options for a collaborator. */ export function toTextMarkerOptions( collaborator: ICollaboratorState ): CodeMirror.TextMarkerOptions { let r = parseInt(collaborator.color.slice(1, 3), 16); let g = parseInt(collaborator.color.slice(3, 5), 16); let b = parseInt(collaborator.color.slice(5, 7), 16); let css = `background-color: rgba( ${r}, ${g}, ${b}, 0.15)`; return { title: collaborator.name, css }; } /** * Convert an editor position to a code mirror position. */ export function toCodeMirrorPosition(position: IPosition) { return { line: position.line, ch: position.column }; } /** * Converts a code mirror selection to an editor selection. */ export function toSelection(selection: { anchor: CodeMirror.Position; head: CodeMirror.Position; }): ITextSelection { return { start: toPosition(selection.anchor), end: toPosition(selection.head) }; } /** * Convert a code mirror position to an editor position. */ export function toPosition(position: CodeMirror.Position): IPosition { return { line: position.line, column: position.ch }; } /** * Gets the selections for all the cursors, never `null` or empty. */ export function getSelections(doc: CodeMirror.Doc): ITextSelection[] { let selections = doc.listSelections(); if (selections.length > 0) { return selections.map(selection => Private.toSelection(selection)); } let cursor = doc.getCursor(); let selection = Private.toSelection({ anchor: cursor, head: cursor }); return [selection]; } } lumino-2021.12.13/examples/example-datastore/src/wsadapter.ts000066400000000000000000000175171415564225700240220ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2018, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { PromiseDelegate } from '@lumino/coreutils'; import { Datastore, IServerAdapter, Schema } from '@lumino/datastore'; import { WSAdapterMessages } from './messages'; import { WSConnection } from './wsbase'; /** * A websocket based adapter for a datastore. */ export class WSAdapter extends WSConnection implements IServerAdapter { /** * Create a new websocket adapter for a datastore. * * @param wsFactory - a factory function wich creates a websocket connection. */ constructor(wsFactory: WSConnection.WSFactory) { super(wsFactory); this._delegates = new Map< string, PromiseDelegate >(); } /** * Create a new datastore connected to the server. * * @param schemas - the schemas for the datastore. * * @returns a promise that resolves with the datastore that was created. * * #### Notes this does not resolve until the datastore is created * and its entire history has been applied. */ async createStore(schemas: Schema[]): Promise { let storeId = await this._createStoreId(); let datastore = Datastore.create({ id: storeId, schemas, adapter: this }); let fetchMsg = WSAdapterMessages.createHistoryRequestMessage(); let historyMsg = await this._requestMessageReply(fetchMsg); if (this.onRemoteTransaction) { for (let t of historyMsg.content.history.transactions) { this.onRemoteTransaction(t); } } return datastore; } /** * Broadcast a transaction to all datastores. * * @param transaction - the transaction to broadcast. */ broadcast(transaction: Datastore.Transaction): void { let msg = WSAdapterMessages.createTransactionBroadcastMessage([ transaction ]); void this._requestMessageReply(msg); } /** * Request an undo by id. * * @param id - The id of the transaction to undo. * * @returns a promise that resolves when the undo is complete. */ async undo(id: string): Promise { let msg = WSAdapterMessages.createUndoRequestMessage(id); let reply = await this._requestMessageReply(msg); this._handleUndo(reply.content.transaction); } /** * Request a redo by id. * * @param id - The id of the transaction to redo. * * @returns a promise that resolves when the redo is complete. */ async redo(id: string): Promise { let msg = WSAdapterMessages.createRedoRequestMessage(id); let reply = await this._requestMessageReply(msg); this._handleRedo(reply.content.transaction); } /** * A callback for when a remote transaction is received by the server adapter. */ get onRemoteTransaction(): | ((transaction: Datastore.Transaction) => void) | null { return this._onRemoteTransaction; } set onRemoteTransaction( value: ((transaction: Datastore.Transaction) => void) | null ) { this._onRemoteTransaction = value; } /** * A callback for when an undo is received by the server adapter. */ get onUndo(): ((transaction: Datastore.Transaction) => void) | null { return this._onUndo; } set onUndo(value: ((transaction: Datastore.Transaction) => void) | null) { this._onUndo = value; } /** * A callback for when a redo is received by the server adapter. */ get onRedo(): ((transaction: Datastore.Transaction) => void) | null { return this._onRedo; } set onRedo(value: ((transaction: Datastore.Transaction) => void) | null) { this._onRedo = value; } /** * Dispose of the resources held by the adapter. */ dispose(): void { this._onRemoteTransaction = null; this._onUndo = null; this._onRedo = null; this._delegates.clear(); super.dispose(); } /** * Process messages received over the websocket. * * @param msg - The decoded message that was received. * * @returns Whether the message was handled. */ protected handleMessage(msg: WSAdapterMessages.IMessage): boolean { if (WSAdapterMessages.isReply(msg)) { let delegate = this._delegates.get(msg.parentId!); if (delegate) { delegate.resolve(msg); return true; } } if (msg.msgType === 'transaction-broadcast') { this._handleTransactions(msg.content.transactions); return true; } if (msg.msgType === 'undo-reply') { this._handleUndo(msg.content.transaction); return true; } if (msg.msgType === 'redo-reply') { this._handleRedo(msg.content.transaction); return true; } return false; } /** * Create a new, unique store id. * * @returns A promise that resolves with the new store id. */ private async _createStoreId(): Promise { await this.ready; let msg = WSAdapterMessages.createStoreIdRequestMessage(); let reply = await this._requestMessageReply(msg); return reply.content.storeId; } /** * Handle an undo message received over the websocket. * * @param transaction - the transaction which should be undone. */ private _handleUndo(transaction: Datastore.Transaction): void { if (this.onUndo) { this.onUndo(transaction); } } /** * Handle an undo message received over the websocket. * * @param transaction - the transaction which should be redone. */ private _handleRedo(transaction: Datastore.Transaction): void { if (this.onRedo) { this.onRedo(transaction); } } /** * Process transactions received over the websocket. * * @param transactions - the transactions which should be applied. */ private _handleTransactions( transactions: ReadonlyArray ): void { if (this.isDisposed) { return; } if (this.onRemoteTransaction) { for (let t of transactions) { this.onRemoteTransaction(t); } } } /** * Send a message to the server and resolve the reply message. * * @param msg: the message to send to the server. * * @returns a the reply from the server. */ private _requestMessageReply( msg: WSAdapterMessages.IStoreIdMessageRequest ): Promise; private _requestMessageReply( msg: WSAdapterMessages.IUndoMessageRequest ): Promise; private _requestMessageReply( msg: WSAdapterMessages.IRedoMessageRequest ): Promise; private _requestMessageReply( msg: WSAdapterMessages.IHistoryRequestMessage ): Promise; private _requestMessageReply( msg: WSAdapterMessages.ITransactionBroadcastMessage ): Promise; private _requestMessageReply( msg: WSAdapterMessages.IMessage ): Promise { let delegate = new PromiseDelegate(); this._delegates.set(msg.msgId, delegate); let promise = delegate.promise.then(reply => { this._delegates.delete(msg.msgId); return reply; }); this.sendMessage(msg); return promise; } private _delegates: Map< string, PromiseDelegate >; private _onRemoteTransaction: | ((transaction: Datastore.Transaction) => void) | null = null; private _onUndo: ((transaction: Datastore.Transaction) => void) | null = null; private _onRedo: ((transaction: Datastore.Transaction) => void) | null = null; } lumino-2021.12.13/examples/example-datastore/src/wsbase.ts000066400000000000000000000101241415564225700232770ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2019, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { JSONValue, PromiseDelegate } from '@lumino/coreutils'; import { IDisposable } from '@lumino/disposable'; /** * Abstract base for a class that sends/receives messages over websocket. */ export abstract class WSConnection implements IDisposable { /** * Create a new websocket based connection. */ constructor(wsFactory: WSConnection.WSFactory) { this._wsFactory = wsFactory; this._ready = new PromiseDelegate(); this._createSocket(); } /** * Dispose of the resources held by the adapter. */ dispose(): void { if (this.isDisposed) { return; } this._isDisposed = true; this._clearSocket(); } /** * Test whether the kernel has been disposed. */ get isDisposed(): boolean { return this._isDisposed; } /** * A promise that resolves once the connection is open. */ get ready(): Promise { return this._ready.promise; } /** * Send a message over the websocket. * * @param msg - The JSON value to send. */ protected sendMessage(msg: T): void { if (!this._ws || this._wsStopped) { throw new Error('Web socket not connected'); } else { this._ws.send(JSON.stringify(msg)); } } /** * Handle a received, decoded WS message. * * @param msg - The decoded message that was received. * * @returns Whether the message was handled. */ protected abstract handleMessage(msg: U): boolean; /** * Create the kernel websocket connection and add socket status handlers. */ private _createSocket = () => { this._wsStopped = false; this._ws = this._wsFactory(); this._ws.onmessage = this._onWSMessage.bind(this); this._ws.onopen = this._onWSOpen.bind(this); this._ws.onclose = this._onWSClose.bind(this); this._ws.onerror = this._onWSClose.bind(this); }; /** * Clear the socket state. */ private _clearSocket(): void { this._wsStopped = true; if (this._ws !== null) { // Clear the websocket event handlers and the socket itself. this._ws.onopen = this._noOp; this._ws.onclose = this._noOp; this._ws.onerror = this._noOp; this._ws.onmessage = this._noOp; this._ws.close(); this._ws = null; } } /** * Handle the websocket opening. */ private _onWSOpen(): void { this._wsStopped = false; this._ready.resolve(undefined); } /** * Handle a message from the websocket. * * @param evt - the message received. */ private _onWSMessage(evt: MessageEvent): void { if (this._wsStopped) { // If the socket is being closed, ignore any messages return; } let msg; try { msg = JSON.parse(evt.data) as U; } catch (error) { console.error(`Invalid message: ${error.message}`); return; } let handled = this.handleMessage(msg); if (!handled) { console.log('Unhandled websocket message.', msg); } } /** * Handle the websocket closing. */ private _onWSClose() { if (this._wsStopped || !this._ws) { return; } // Clear the websocket event handlers and the socket itself. this._ws.onclose = this._noOp; this._ws.onerror = this._noOp; this._ws = null; this._wsStopped = true; } private _wsFactory: WSConnection.WSFactory; private _ws: WebSocket | null = null; private _wsStopped: boolean; private _isDisposed = false; private _ready: PromiseDelegate; private _noOp = () => { /* no-op */ }; } /** * The namespace for WSConnection statics. */ export namespace WSConnection { /** * A websocket factory function. */ export type WSFactory = () => WebSocket; } lumino-2021.12.13/examples/example-datastore/style/000077500000000000000000000000001415564225700220165ustar00rootroot00000000000000lumino-2021.12.13/examples/example-datastore/style/index.css000066400000000000000000000037001415564225700236370ustar00rootroot00000000000000/*----------------------------------------------------------------------------- | Copyright (c) Jupyter Development Team. | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ @import '~@lumino/dragdrop/style/index.css'; @import '~@lumino/widgets/style/index.css'; @import './tabbar.css'; @import '~codemirror/lib/codemirror.css'; body { display: flex; flex-direction: column; position: absolute; top: 0; left: 0; right: 0; bottom: 0; margin: 0; padding: 0; overflow: hidden; } #main { flex: 1 1 auto; } .lm-Dockpanel-widget .CodeMirror { width: 100%; height: 100%; margin: 16px; } #dock { padding: 4px; background: slategray; } .lm-DockPanel-overlay { background: rgba(255, 255, 255, 0.8); border: 1px dashed black; transition-property: top, left, right, bottom; transition-duration: 150ms; transition-timing-function: ease; } .content { min-width: 50px; min-height: 50px; border: 1px solid #c0c0c0; border-top: none; background: white; box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.2); } .read-only-check { font-size: 12px; height: 24px; line-height: 24px; box-shadow: 0px 0px 5px 0px rgba(0, 0, 0, 0.25); z-index: 10; display: flex; justify-content: flex-end; align-items: center; } .collaborator-cursor { border-left: 5px solid transparent; border-right: 5px solid transparent; border-top: none; border-bottom: 3px solid; background-clip: content-box; margin-left: -5px; margin-right: -5px; } .collaborator-cursor-hover { position: absolute; z-index: 1; transform: translateX(-50%); color: white; border-radius: 3px; padding-left: 4px; padding-right: 4px; padding-top: 1px; padding-bottom: 1px; text-align: center; font-size: 9pt; white-space: nowrap; } lumino-2021.12.13/examples/example-datastore/style/tabbar.css000066400000000000000000000031331415564225700237630ustar00rootroot00000000000000/*----------------------------------------------------------------------------- | Copyright (c) Jupyter Development Team. | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ .lm-TabBar { min-height: 24px; max-height: 24px; } .lm-TabBar-content { min-width: 0; min-height: 0; align-items: flex-end; border-bottom: 1px solid #c0c0c0; } .lm-TabBar-tab { padding: 0px 10px; background: #e5e5e5; border: 1px solid #c0c0c0; border-bottom: none; font: 12px Helvetica, Arial, sans-serif; flex: 0 1 125px; min-height: 20px; max-height: 20px; min-width: 35px; margin-left: -1px; line-height: 20px; } .lm-TabBar-tab.lm-mod-current { background: white; } .lm-TabBar-tab:hover:not(.lm-mod-current) { background: #f0f0f0; } .lm-TabBar-tab:first-child { margin-left: 0; } .lm-TabBar-tab.lm-mod-current { min-height: 23px; max-height: 23px; transform: translateY(1px); } .lm-TabBar-tabIcon, .lm-TabBar-tabLabel, .lm-TabBar-tabCloseIcon { display: inline-block; } .lm-TabBar-tab.lm-mod-closable > .lm-TabBar-tabCloseIcon { margin-left: 4px; } .lm-TabBar-tab.lm-mod-closable > .lm-TabBar-tabCloseIcon:before { content: '\f00d'; font-family: FontAwesome; } .lm-TabBar-tab.lm-mod-drag-image { min-height: 23px; max-height: 23px; min-width: 125px; border: none; box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.3); transform: translateX(-40%) translateY(-58%); } lumino-2021.12.13/examples/example-datastore/tsconfig.json000066400000000000000000000006621415564225700233710ustar00rootroot00000000000000{ "compilerOptions": { "declaration": false, "noImplicitAny": true, "noEmitOnError": true, "noUnusedLocals": true, "strictNullChecks": true, "module": "commonjs", "moduleResolution": "node", "target": "ES5", "outDir": "./build", "lib": [ "ES5", "es2015.collection", "DOM", "ES2015.Promise", "ES2015.Iterable" ], "types": [] }, "include": ["src/*"] } lumino-2021.12.13/examples/example-datastore/webpack.config.js000066400000000000000000000005651415564225700241020ustar00rootroot00000000000000var path = require('path'); module.exports = { entry: './build/index.js', mode: 'development', output: { path: __dirname + '/build/', filename: 'bundle.example.js', publicPath: './build/' }, module: { rules: [ { test: /\.css$/, use: ['style-loader', 'css-loader'] }, { test: /\.png$/, use: 'file-loader' } ] }, plugins: [] }; lumino-2021.12.13/examples/example-dockpanel-amd/000077500000000000000000000000001415564225700213675ustar00rootroot00000000000000lumino-2021.12.13/examples/example-dockpanel-amd/README.md000066400000000000000000000010251415564225700226440ustar00rootroot00000000000000# example-dockpanel-amd _AMD_ version of [example-dockpanel](../example-dockpanel) ## Prerequisites From the root lumino folder run: ``` yarn install yarn run build yarn run minimize ``` You should now be able to open [index.html](./index.html) directly into your web browser. ## Notable differences - All dependencies loaded via AMD + RequireJS, see `head` section of [index.html](./index.html) - TypeScript converted to IE compatible JavaScript - CSS files manually imported as needed via [style/index.css](style/index.css) lumino-2021.12.13/examples/example-dockpanel-amd/index.html000066400000000000000000000032241415564225700233650ustar00rootroot00000000000000 example-dockpanel-iife lumino-2021.12.13/examples/example-dockpanel-amd/package.json000066400000000000000000000003051415564225700236530ustar00rootroot00000000000000{ "name": "@lumino/example-dockpanel-amd", "version": "0.4.0", "private": true, "scripts": { "test": "node ./test/runner.js" }, "devDependencies": { "puppeteer": "^2.1.1" } } lumino-2021.12.13/examples/example-dockpanel-amd/src/000077500000000000000000000000001415564225700221565ustar00rootroot00000000000000lumino-2021.12.13/examples/example-dockpanel-amd/src/index.js000066400000000000000000000272271415564225700236350ustar00rootroot00000000000000/* eslint-disable @typescript-eslint/no-empty-function */ // Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. define(['@lumino/commands', '@lumino/widgets'], function ( lumino_commands, lumino_widgets ) { const CommandRegistry = lumino_commands.CommandRegistry; const BoxPanel = lumino_widgets.BoxPanel; const CommandPalette = lumino_widgets.CommandPalette; const ContextMenu = lumino_widgets.ContextMenu; const DockPanel = lumino_widgets.DockPanel; const Menu = lumino_widgets.Menu; const MenuBar = lumino_widgets.MenuBar; const Widget = lumino_widgets.Widget; const commands = new CommandRegistry(); function createMenu() { let sub1 = new Menu({ commands: commands }); sub1.title.label = 'More...'; sub1.title.mnemonic = 0; sub1.addItem({ command: 'example:one' }); sub1.addItem({ command: 'example:two' }); sub1.addItem({ command: 'example:three' }); sub1.addItem({ command: 'example:four' }); let sub2 = new Menu({ commands: commands }); sub2.title.label = 'More...'; sub2.title.mnemonic = 0; sub2.addItem({ command: 'example:one' }); sub2.addItem({ command: 'example:two' }); sub2.addItem({ command: 'example:three' }); sub2.addItem({ command: 'example:four' }); sub2.addItem({ type: 'submenu', submenu: sub1 }); let root = new Menu({ commands: commands }); root.addItem({ command: 'example:copy' }); root.addItem({ command: 'example:cut' }); root.addItem({ command: 'example:paste' }); root.addItem({ type: 'separator' }); root.addItem({ command: 'example:new-tab' }); root.addItem({ command: 'example:close-tab' }); root.addItem({ command: 'example:save-on-exit' }); root.addItem({ type: 'separator' }); root.addItem({ command: 'example:open-task-manager' }); root.addItem({ type: 'separator' }); root.addItem({ type: 'submenu', submenu: sub2 }); root.addItem({ type: 'separator' }); root.addItem({ command: 'example:close' }); return root; } function ContentWidget(name) { Widget.call(this, { node: ContentWidget.prototype.createNode() }); this.setFlag(Widget.Flag.DisallowLayout); this.addClass('content'); this.addClass(name.toLowerCase()); this.title.label = name; this.title.closable = true; this.title.caption = 'Long description for: ' + name; } ContentWidget.prototype = Object.create(Widget.prototype); ContentWidget.prototype.createNode = function () { let node = document.createElement('div'); let content = document.createElement('div'); let input = document.createElement('input'); input.placeholder = 'Placeholder...'; content.appendChild(input); node.appendChild(content); return node; }; ContentWidget.prototype.inputNode = function () { return this.node.getElementsByTagName('input')[0]; }; ContentWidget.prototype.onActivateRequest = function (msg) { if (this.isAttached) { this.inputNode().focus(); } }; function main() { commands.addCommand('example:cut', { label: 'Cut', mnemonic: 1, iconClass: 'fa fa-cut', execute: function () { console.log('Cut'); } }); commands.addCommand('example:copy', { label: 'Copy File', mnemonic: 0, iconClass: 'fa fa-copy', execute: function () { console.log('Copy'); } }); commands.addCommand('example:paste', { label: 'Paste', mnemonic: 0, iconClass: 'fa fa-paste', execute: function () { console.log('Paste'); } }); commands.addCommand('example:new-tab', { label: 'New Tab', mnemonic: 0, caption: 'Open a new tab', execute: function () { console.log('New Tab'); } }); commands.addCommand('example:close-tab', { label: 'Close Tab', mnemonic: 2, caption: 'Close the current tab', execute: function () { console.log('Close Tab'); } }); commands.addCommand('example:save-on-exit', { label: 'Save on Exit', mnemonic: 0, caption: 'Toggle the save on exit flag', execute: function () { console.log('Save on Exit'); } }); commands.addCommand('example:open-task-manager', { label: 'Task Manager', mnemonic: 5, isEnabled: function () { return false; }, execute: function () {} }); commands.addCommand('example:close', { label: 'Close', mnemonic: 0, iconClass: 'fa fa-close', execute: function () { console.log('Close'); } }); commands.addCommand('example:one', { label: 'One', execute: function () { console.log('One'); } }); commands.addCommand('example:two', { label: 'Two', execute: function () { console.log('Two'); } }); commands.addCommand('example:three', { label: 'Three', execute: function () { console.log('Three'); } }); commands.addCommand('example:four', { label: 'Four', execute: function () { console.log('Four'); } }); commands.addCommand('example:black', { label: 'Black', execute: function () { console.log('Black'); } }); commands.addCommand('example:clear-cell', { label: 'Clear Cell', execute: function () { console.log('Clear Cell'); } }); commands.addCommand('example:cut-cells', { label: 'Cut Cell(s)', execute: function () { console.log('Cut Cell(s)'); } }); commands.addCommand('example:run-cell', { label: 'Run Cell', execute: function () { console.log('Run Cell'); } }); commands.addCommand('example:cell-test', { label: 'Cell Test', execute: function () { console.log('Cell Test'); } }); commands.addCommand('notebook:new', { label: 'New Notebook', execute: function () { console.log('New Notebook'); } }); commands.addKeyBinding({ keys: ['Accel X'], selector: 'body', command: 'example:cut' }); commands.addKeyBinding({ keys: ['Accel C'], selector: 'body', command: 'example:copy' }); commands.addKeyBinding({ keys: ['Accel V'], selector: 'body', command: 'example:paste' }); commands.addKeyBinding({ keys: ['Accel J', 'Accel J'], selector: 'body', command: 'example:new-tab' }); commands.addKeyBinding({ keys: ['Accel M'], selector: 'body', command: 'example:open-task-manager' }); let menu1 = createMenu(); menu1.title.label = 'File'; menu1.title.mnemonic = 0; let menu2 = createMenu(); menu2.title.label = 'Edit'; menu2.title.mnemonic = 0; let menu3 = createMenu(); menu3.title.label = 'View'; menu3.title.mnemonic = 0; let bar = new MenuBar(); bar.addMenu(menu1); bar.addMenu(menu2); bar.addMenu(menu3); bar.id = 'menuBar'; let palette = new CommandPalette({ commands: commands }); palette.addItem({ command: 'example:cut', category: 'Edit' }); palette.addItem({ command: 'example:copy', category: 'Edit' }); palette.addItem({ command: 'example:paste', category: 'Edit' }); palette.addItem({ command: 'example:one', category: 'Number' }); palette.addItem({ command: 'example:two', category: 'Number' }); palette.addItem({ command: 'example:three', category: 'Number' }); palette.addItem({ command: 'example:four', category: 'Number' }); palette.addItem({ command: 'example:black', category: 'Number' }); palette.addItem({ command: 'example:new-tab', category: 'File' }); palette.addItem({ command: 'example:close-tab', category: 'File' }); palette.addItem({ command: 'example:save-on-exit', category: 'File' }); palette.addItem({ command: 'example:open-task-manager', category: 'File' }); palette.addItem({ command: 'example:close', category: 'File' }); palette.addItem({ command: 'example:clear-cell', category: 'Notebook Cell Operations' }); palette.addItem({ command: 'example:cut-cells', category: 'Notebook Cell Operations' }); palette.addItem({ command: 'example:run-cell', category: 'Notebook Cell Operations' }); palette.addItem({ command: 'example:cell-test', category: 'Console' }); palette.addItem({ command: 'notebook:new', category: 'Notebook' }); palette.id = 'palette'; let contextMenu = new ContextMenu({ commands: commands }); document.addEventListener('contextmenu', function (event) { if (contextMenu.open(event)) { event.preventDefault(); } }); contextMenu.addItem({ command: 'example:cut', selector: '.content' }); contextMenu.addItem({ command: 'example:copy', selector: '.content' }); contextMenu.addItem({ command: 'example:paste', selector: '.content' }); contextMenu.addItem({ command: 'example:one', selector: '.lm-CommandPalette' }); contextMenu.addItem({ command: 'example:two', selector: '.lm-CommandPalette' }); contextMenu.addItem({ command: 'example:three', selector: '.lm-CommandPalette' }); contextMenu.addItem({ command: 'example:four', selector: '.lm-CommandPalette' }); contextMenu.addItem({ command: 'example:black', selector: '.lm-CommandPalette' }); contextMenu.addItem({ command: 'notebook:new', selector: '.lm-CommandPalette-input' }); contextMenu.addItem({ command: 'example:save-on-exit', selector: '.lm-CommandPalette-input' }); contextMenu.addItem({ command: 'example:open-task-manager', selector: '.lm-CommandPalette-input' }); contextMenu.addItem({ type: 'separator', selector: '.lm-CommandPalette-input' }); document.addEventListener('keydown', function (event) { commands.processKeydownEvent(event); }); let r1 = new ContentWidget('Red'); let b1 = new ContentWidget('Blue'); let g1 = new ContentWidget('Green'); let y1 = new ContentWidget('Yellow'); let r2 = new ContentWidget('Red'); let b2 = new ContentWidget('Blue'); // let g2 = new ContentWidget('Green'); // let y2 = new ContentWidget('Yellow'); let dock = new DockPanel(); dock.addWidget(r1); dock.addWidget(b1, { mode: 'split-right', ref: r1 }); dock.addWidget(y1, { mode: 'split-bottom', ref: b1 }); dock.addWidget(g1, { mode: 'split-left', ref: y1 }); dock.addWidget(r2, { ref: b1 }); dock.addWidget(b2, { mode: 'split-right', ref: y1 }); dock.id = 'dock'; let savedLayouts = []; commands.addCommand('save-dock-layout', { label: 'Save Layout', caption: 'Save the current dock layout', execute: function () { savedLayouts.push(dock.saveLayout()); palette.addItem({ command: 'restore-dock-layout', category: 'Dock Layout', args: { index: savedLayouts.length - 1 } }); } }); commands.addCommand('restore-dock-layout', { label: function (args) { return 'Restore Layout ' + args.index; }, execute: function (args) { dock.restoreLayout(savedLayouts[args.index]); } }); palette.addItem({ command: 'save-dock-layout', category: 'Dock Layout', rank: 0 }); BoxPanel.setStretch(dock, 1); let main = new BoxPanel({ direction: 'left-to-right', spacing: 0 }); main.id = 'main'; main.addWidget(palette); main.addWidget(dock); window.onresize = function () { main.update(); }; Widget.attach(bar, document.body); Widget.attach(main, document.body); } return main; }); lumino-2021.12.13/examples/example-dockpanel-amd/style/000077500000000000000000000000001415564225700225275ustar00rootroot00000000000000lumino-2021.12.13/examples/example-dockpanel-amd/style/content.css000066400000000000000000000016051415564225700247150ustar00rootroot00000000000000/*----------------------------------------------------------------------------- | Copyright (c) Jupyter Development Team. | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ .content { min-width: 50px; min-height: 50px; display: flex; flex-direction: column; padding: 8px; border: 1px solid #c0c0c0; border-top: none; background: white; box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.2); } .content > div { flex: 1 1 auto; border: 1px solid #505050; overflow: auto; } .content input { margin: 8px; } .red > div { background: #e74c3c; } .yellow > div { background: #f1c40f; } .green > div { background: #27ae60; } .blue > div { background: #3498db; } lumino-2021.12.13/examples/example-dockpanel-amd/style/index.css000066400000000000000000000024041415564225700243500ustar00rootroot00000000000000/*----------------------------------------------------------------------------- | Copyright (c) Jupyter Development Team. | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ @import '../../../packages/dragdrop/style/index.css'; @import '../../../packages/widgets/style/index.css'; @import '../../../packages/default-theme/style/commandpalette.css'; @import '../../../packages/default-theme/style/datagrid.css'; @import '../../../packages/default-theme/style/dockpanel.css'; @import '../../../packages/default-theme/style/menu.css'; @import '../../../packages/default-theme/style/menubar.css'; @import '../../../packages/default-theme/style/scrollbar.css'; @import '../../../packages/default-theme/style/tabbar.css'; @import './content.css'; body { display: flex; flex-direction: column; position: absolute; top: 0; left: 0; right: 0; bottom: 0; margin: 0; padding: 0; overflow: hidden; } #menuBar { flex: 0 0 auto; } #main { flex: 1 1 auto; } #palette { min-width: 300px; border-right: 1px solid #dddddd; } #dock { padding: 4px; } lumino-2021.12.13/examples/example-dockpanel-amd/test/000077500000000000000000000000001415564225700223465ustar00rootroot00000000000000lumino-2021.12.13/examples/example-dockpanel-amd/test/runner.js000066400000000000000000000010171415564225700242140ustar00rootroot00000000000000const puppeteer = require('puppeteer'); const url = `file://${process.cwd()}/index.html`; (async () => { let success = true; const browser = await puppeteer.launch(); const page = await browser.newPage(); page.on('error', err => { console.log('Error: ', err); success = false; }); page.on('pageerror', pageerr => { console.log('Page Error: ', pageerr); success = false; }); await page.goto(url, { timeout: 30000 }); await browser.close(); process.exit(success === true ? 0 : 1); })(); lumino-2021.12.13/examples/example-dockpanel-iife/000077500000000000000000000000001415564225700215425ustar00rootroot00000000000000lumino-2021.12.13/examples/example-dockpanel-iife/README.md000066400000000000000000000010051415564225700230150ustar00rootroot00000000000000# example-dockpanel-iife _IIFE_ version of [example-dockpanel](../example-dockpanel) ## Prerequisites From the root lumino folder run: ``` yarn install yarn run build yarn run minimize ``` You should now be able to open [index.html](./index.html) directly into your web browser. ## Notable differences - All dependencies loaded in the `head` section of [index.html](./index.html) - TypeScript converted to IE compatible JavaScript - CSS files manually imported as needed via [style/index.css](style/index.css) lumino-2021.12.13/examples/example-dockpanel-iife/index.html000066400000000000000000000024221415564225700235370ustar00rootroot00000000000000 example-dockpanel-iife lumino-2021.12.13/examples/example-dockpanel-iife/package.json000066400000000000000000000003061415564225700240270ustar00rootroot00000000000000{ "name": "@lumino/example-dockpanel-iife", "version": "0.4.0", "private": true, "scripts": { "test": "node ./test/runner.js" }, "devDependencies": { "puppeteer": "^2.1.1" } } lumino-2021.12.13/examples/example-dockpanel-iife/src/000077500000000000000000000000001415564225700223315ustar00rootroot00000000000000lumino-2021.12.13/examples/example-dockpanel-iife/src/index.js000066400000000000000000000255431415564225700240070ustar00rootroot00000000000000/* eslint-disable @typescript-eslint/no-empty-function */ // Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. const CommandRegistry = lumino_commands.CommandRegistry; const BoxPanel = lumino_widgets.BoxPanel; const CommandPalette = lumino_widgets.CommandPalette; const ContextMenu = lumino_widgets.ContextMenu; const DockPanel = lumino_widgets.DockPanel; const Menu = lumino_widgets.Menu; const MenuBar = lumino_widgets.MenuBar; const Widget = lumino_widgets.Widget; const commands = new CommandRegistry(); function createMenu() { let sub1 = new Menu({ commands: commands }); sub1.title.label = 'More...'; sub1.title.mnemonic = 0; sub1.addItem({ command: 'example:one' }); sub1.addItem({ command: 'example:two' }); sub1.addItem({ command: 'example:three' }); sub1.addItem({ command: 'example:four' }); let sub2 = new Menu({ commands: commands }); sub2.title.label = 'More...'; sub2.title.mnemonic = 0; sub2.addItem({ command: 'example:one' }); sub2.addItem({ command: 'example:two' }); sub2.addItem({ command: 'example:three' }); sub2.addItem({ command: 'example:four' }); sub2.addItem({ type: 'submenu', submenu: sub1 }); let root = new Menu({ commands: commands }); root.addItem({ command: 'example:copy' }); root.addItem({ command: 'example:cut' }); root.addItem({ command: 'example:paste' }); root.addItem({ type: 'separator' }); root.addItem({ command: 'example:new-tab' }); root.addItem({ command: 'example:close-tab' }); root.addItem({ command: 'example:save-on-exit' }); root.addItem({ type: 'separator' }); root.addItem({ command: 'example:open-task-manager' }); root.addItem({ type: 'separator' }); root.addItem({ type: 'submenu', submenu: sub2 }); root.addItem({ type: 'separator' }); root.addItem({ command: 'example:close' }); return root; } function ContentWidget(name) { Widget.call(this, { node: ContentWidget.prototype.createNode() }); this.setFlag(Widget.Flag.DisallowLayout); this.addClass('content'); this.addClass(name.toLowerCase()); this.title.label = name; this.title.closable = true; this.title.caption = 'Long description for: ' + name; } ContentWidget.prototype = Object.create(Widget.prototype); ContentWidget.prototype.createNode = function () { let node = document.createElement('div'); let content = document.createElement('div'); let input = document.createElement('input'); input.placeholder = 'Placeholder...'; content.appendChild(input); node.appendChild(content); return node; }; ContentWidget.prototype.inputNode = function () { return this.node.getElementsByTagName('input')[0]; }; ContentWidget.prototype.onActivateRequest = function (msg) { if (this.isAttached) { this.inputNode().focus(); } }; function main() { commands.addCommand('example:cut', { label: 'Cut', mnemonic: 1, iconClass: 'fa fa-cut', execute: function () { console.log('Cut'); } }); commands.addCommand('example:copy', { label: 'Copy File', mnemonic: 0, iconClass: 'fa fa-copy', execute: function () { console.log('Copy'); } }); commands.addCommand('example:paste', { label: 'Paste', mnemonic: 0, iconClass: 'fa fa-paste', execute: function () { console.log('Paste'); } }); commands.addCommand('example:new-tab', { label: 'New Tab', mnemonic: 0, caption: 'Open a new tab', execute: function () { console.log('New Tab'); } }); commands.addCommand('example:close-tab', { label: 'Close Tab', mnemonic: 2, caption: 'Close the current tab', execute: function () { console.log('Close Tab'); } }); commands.addCommand('example:save-on-exit', { label: 'Save on Exit', mnemonic: 0, caption: 'Toggle the save on exit flag', execute: function () { console.log('Save on Exit'); } }); commands.addCommand('example:open-task-manager', { label: 'Task Manager', mnemonic: 5, isEnabled: function () { return false; }, execute: function () {} }); commands.addCommand('example:close', { label: 'Close', mnemonic: 0, iconClass: 'fa fa-close', execute: function () { console.log('Close'); } }); commands.addCommand('example:one', { label: 'One', execute: function () { console.log('One'); } }); commands.addCommand('example:two', { label: 'Two', execute: function () { console.log('Two'); } }); commands.addCommand('example:three', { label: 'Three', execute: function () { console.log('Three'); } }); commands.addCommand('example:four', { label: 'Four', execute: function () { console.log('Four'); } }); commands.addCommand('example:black', { label: 'Black', execute: function () { console.log('Black'); } }); commands.addCommand('example:clear-cell', { label: 'Clear Cell', execute: function () { console.log('Clear Cell'); } }); commands.addCommand('example:cut-cells', { label: 'Cut Cell(s)', execute: function () { console.log('Cut Cell(s)'); } }); commands.addCommand('example:run-cell', { label: 'Run Cell', execute: function () { console.log('Run Cell'); } }); commands.addCommand('example:cell-test', { label: 'Cell Test', execute: function () { console.log('Cell Test'); } }); commands.addCommand('notebook:new', { label: 'New Notebook', execute: function () { console.log('New Notebook'); } }); commands.addKeyBinding({ keys: ['Accel X'], selector: 'body', command: 'example:cut' }); commands.addKeyBinding({ keys: ['Accel C'], selector: 'body', command: 'example:copy' }); commands.addKeyBinding({ keys: ['Accel V'], selector: 'body', command: 'example:paste' }); commands.addKeyBinding({ keys: ['Accel J', 'Accel J'], selector: 'body', command: 'example:new-tab' }); commands.addKeyBinding({ keys: ['Accel M'], selector: 'body', command: 'example:open-task-manager' }); let menu1 = createMenu(); menu1.title.label = 'File'; menu1.title.mnemonic = 0; let menu2 = createMenu(); menu2.title.label = 'Edit'; menu2.title.mnemonic = 0; let menu3 = createMenu(); menu3.title.label = 'View'; menu3.title.mnemonic = 0; let bar = new MenuBar(); bar.addMenu(menu1); bar.addMenu(menu2); bar.addMenu(menu3); bar.id = 'menuBar'; let palette = new CommandPalette({ commands: commands }); palette.addItem({ command: 'example:cut', category: 'Edit' }); palette.addItem({ command: 'example:copy', category: 'Edit' }); palette.addItem({ command: 'example:paste', category: 'Edit' }); palette.addItem({ command: 'example:one', category: 'Number' }); palette.addItem({ command: 'example:two', category: 'Number' }); palette.addItem({ command: 'example:three', category: 'Number' }); palette.addItem({ command: 'example:four', category: 'Number' }); palette.addItem({ command: 'example:black', category: 'Number' }); palette.addItem({ command: 'example:new-tab', category: 'File' }); palette.addItem({ command: 'example:close-tab', category: 'File' }); palette.addItem({ command: 'example:save-on-exit', category: 'File' }); palette.addItem({ command: 'example:open-task-manager', category: 'File' }); palette.addItem({ command: 'example:close', category: 'File' }); palette.addItem({ command: 'example:clear-cell', category: 'Notebook Cell Operations' }); palette.addItem({ command: 'example:cut-cells', category: 'Notebook Cell Operations' }); palette.addItem({ command: 'example:run-cell', category: 'Notebook Cell Operations' }); palette.addItem({ command: 'example:cell-test', category: 'Console' }); palette.addItem({ command: 'notebook:new', category: 'Notebook' }); palette.id = 'palette'; let contextMenu = new ContextMenu({ commands: commands }); document.addEventListener('contextmenu', function (event) { if (contextMenu.open(event)) { event.preventDefault(); } }); contextMenu.addItem({ command: 'example:cut', selector: '.content' }); contextMenu.addItem({ command: 'example:copy', selector: '.content' }); contextMenu.addItem({ command: 'example:paste', selector: '.content' }); contextMenu.addItem({ command: 'example:one', selector: '.lm-CommandPalette' }); contextMenu.addItem({ command: 'example:two', selector: '.lm-CommandPalette' }); contextMenu.addItem({ command: 'example:three', selector: '.lm-CommandPalette' }); contextMenu.addItem({ command: 'example:four', selector: '.lm-CommandPalette' }); contextMenu.addItem({ command: 'example:black', selector: '.lm-CommandPalette' }); contextMenu.addItem({ command: 'notebook:new', selector: '.lm-CommandPalette-input' }); contextMenu.addItem({ command: 'example:save-on-exit', selector: '.lm-CommandPalette-input' }); contextMenu.addItem({ command: 'example:open-task-manager', selector: '.lm-CommandPalette-input' }); contextMenu.addItem({ type: 'separator', selector: '.lm-CommandPalette-input' }); document.addEventListener('keydown', function (event) { commands.processKeydownEvent(event); }); let r1 = new ContentWidget('Red'); let b1 = new ContentWidget('Blue'); let g1 = new ContentWidget('Green'); let y1 = new ContentWidget('Yellow'); let r2 = new ContentWidget('Red'); let b2 = new ContentWidget('Blue'); // let g2 = new ContentWidget('Green'); // let y2 = new ContentWidget('Yellow'); let dock = new DockPanel(); dock.addWidget(r1); dock.addWidget(b1, { mode: 'split-right', ref: r1 }); dock.addWidget(y1, { mode: 'split-bottom', ref: b1 }); dock.addWidget(g1, { mode: 'split-left', ref: y1 }); dock.addWidget(r2, { ref: b1 }); dock.addWidget(b2, { mode: 'split-right', ref: y1 }); dock.id = 'dock'; let savedLayouts = []; commands.addCommand('save-dock-layout', { label: 'Save Layout', caption: 'Save the current dock layout', execute: function () { savedLayouts.push(dock.saveLayout()); palette.addItem({ command: 'restore-dock-layout', category: 'Dock Layout', args: { index: savedLayouts.length - 1 } }); } }); commands.addCommand('restore-dock-layout', { label: function (args) { return 'Restore Layout ' + args.index; }, execute: function (args) { dock.restoreLayout(savedLayouts[args.index]); } }); palette.addItem({ command: 'save-dock-layout', category: 'Dock Layout', rank: 0 }); BoxPanel.setStretch(dock, 1); let main = new BoxPanel({ direction: 'left-to-right', spacing: 0 }); main.id = 'main'; main.addWidget(palette); main.addWidget(dock); window.onresize = function () { main.update(); }; Widget.attach(bar, document.body); Widget.attach(main, document.body); } window.onload = main; lumino-2021.12.13/examples/example-dockpanel-iife/style/000077500000000000000000000000001415564225700227025ustar00rootroot00000000000000lumino-2021.12.13/examples/example-dockpanel-iife/style/content.css000066400000000000000000000016051415564225700250700ustar00rootroot00000000000000/*----------------------------------------------------------------------------- | Copyright (c) Jupyter Development Team. | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ .content { min-width: 50px; min-height: 50px; display: flex; flex-direction: column; padding: 8px; border: 1px solid #c0c0c0; border-top: none; background: white; box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.2); } .content > div { flex: 1 1 auto; border: 1px solid #505050; overflow: auto; } .content input { margin: 8px; } .red > div { background: #e74c3c; } .yellow > div { background: #f1c40f; } .green > div { background: #27ae60; } .blue > div { background: #3498db; } lumino-2021.12.13/examples/example-dockpanel-iife/style/index.css000066400000000000000000000024041415564225700245230ustar00rootroot00000000000000/*----------------------------------------------------------------------------- | Copyright (c) Jupyter Development Team. | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ @import '../../../packages/dragdrop/style/index.css'; @import '../../../packages/widgets/style/index.css'; @import '../../../packages/default-theme/style/commandpalette.css'; @import '../../../packages/default-theme/style/datagrid.css'; @import '../../../packages/default-theme/style/dockpanel.css'; @import '../../../packages/default-theme/style/menu.css'; @import '../../../packages/default-theme/style/menubar.css'; @import '../../../packages/default-theme/style/scrollbar.css'; @import '../../../packages/default-theme/style/tabbar.css'; @import './content.css'; body { display: flex; flex-direction: column; position: absolute; top: 0; left: 0; right: 0; bottom: 0; margin: 0; padding: 0; overflow: hidden; } #menuBar { flex: 0 0 auto; } #main { flex: 1 1 auto; } #palette { min-width: 300px; border-right: 1px solid #dddddd; } #dock { padding: 4px; } lumino-2021.12.13/examples/example-dockpanel-iife/test/000077500000000000000000000000001415564225700225215ustar00rootroot00000000000000lumino-2021.12.13/examples/example-dockpanel-iife/test/runner.js000066400000000000000000000010171415564225700243670ustar00rootroot00000000000000const puppeteer = require('puppeteer'); const url = `file://${process.cwd()}/index.html`; (async () => { let success = true; const browser = await puppeteer.launch(); const page = await browser.newPage(); page.on('error', err => { console.log('Error: ', err); success = false; }); page.on('pageerror', pageerr => { console.log('Page Error: ', pageerr); success = false; }); await page.goto(url, { timeout: 30000 }); await browser.close(); process.exit(success === true ? 0 : 1); })(); lumino-2021.12.13/examples/example-dockpanel/000077500000000000000000000000001415564225700206305ustar00rootroot00000000000000lumino-2021.12.13/examples/example-dockpanel/index.html000066400000000000000000000003631415564225700226270ustar00rootroot00000000000000 lumino-2021.12.13/examples/example-dockpanel/package.json000066400000000000000000000012141415564225700231140ustar00rootroot00000000000000{ "name": "@lumino/example-dockpanel", "version": "0.19.1", "private": true, "scripts": { "build": "tsc && webpack", "clean": "rimraf build" }, "dependencies": { "@lumino/commands": "^1.19.1", "@lumino/default-theme": "^0.20.2", "@lumino/dragdrop": "^1.13.1", "@lumino/messaging": "^1.10.1", "@lumino/widgets": "^1.30.1", "es6-promise": "^4.0.5" }, "devDependencies": { "css-loader": "^3.4.0", "file-loader": "^5.0.2", "rimraf": "^3.0.2", "source-map-loader": "0.2.4", "style-loader": "^1.0.2", "typescript": "~3.6.0", "webpack": "^4.41.3", "webpack-cli": "^3.3.10" } } lumino-2021.12.13/examples/example-dockpanel/src/000077500000000000000000000000001415564225700214175ustar00rootroot00000000000000lumino-2021.12.13/examples/example-dockpanel/src/index.ts000066400000000000000000000267661415564225700231170ustar00rootroot00000000000000/* eslint-disable @typescript-eslint/no-empty-function */ // Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import 'es6-promise/auto'; // polyfill Promise on IE import { CommandRegistry } from '@lumino/commands'; import { Message } from '@lumino/messaging'; import { BoxPanel, CommandPalette, ContextMenu, DockPanel, Menu, MenuBar, TabBar, Widget } from '@lumino/widgets'; import '../style/index.css'; const commands = new CommandRegistry(); function createMenu(): Menu { let sub1 = new Menu({ commands }); sub1.title.label = 'More...'; sub1.title.mnemonic = 0; sub1.addItem({ command: 'example:one' }); sub1.addItem({ command: 'example:two' }); sub1.addItem({ command: 'example:three' }); sub1.addItem({ command: 'example:four' }); let sub2 = new Menu({ commands }); sub2.title.label = 'More...'; sub2.title.mnemonic = 0; sub2.addItem({ command: 'example:one' }); sub2.addItem({ command: 'example:two' }); sub2.addItem({ command: 'example:three' }); sub2.addItem({ command: 'example:four' }); sub2.addItem({ type: 'submenu', submenu: sub1 }); let root = new Menu({ commands }); root.addItem({ command: 'example:copy' }); root.addItem({ command: 'example:cut' }); root.addItem({ command: 'example:paste' }); root.addItem({ type: 'separator' }); root.addItem({ command: 'example:new-tab' }); root.addItem({ command: 'example:close-tab' }); root.addItem({ command: 'example:save-on-exit' }); root.addItem({ type: 'separator' }); root.addItem({ command: 'example:open-task-manager' }); root.addItem({ type: 'separator' }); root.addItem({ type: 'submenu', submenu: sub2 }); root.addItem({ type: 'separator' }); root.addItem({ command: 'example:close' }); return root; } class ContentWidget extends Widget { static createNode(): HTMLElement { let node = document.createElement('div'); let content = document.createElement('div'); let input = document.createElement('input'); input.placeholder = 'Placeholder...'; content.appendChild(input); node.appendChild(content); return node; } constructor(name: string) { super({ node: ContentWidget.createNode() }); this.setFlag(Widget.Flag.DisallowLayout); this.addClass('content'); this.addClass(name.toLowerCase()); this.title.label = name; this.title.closable = true; this.title.caption = `Long description for: ${name}`; } get inputNode(): HTMLInputElement { return this.node.getElementsByTagName('input')[0] as HTMLInputElement; } protected onActivateRequest(msg: Message): void { if (this.isAttached) { this.inputNode.focus(); } } } function main(): void { commands.addCommand('example:cut', { label: 'Cut', mnemonic: 1, iconClass: 'fa fa-cut', execute: () => { console.log('Cut'); } }); commands.addCommand('example:copy', { label: 'Copy File', mnemonic: 0, iconClass: 'fa fa-copy', execute: () => { console.log('Copy'); } }); commands.addCommand('example:paste', { label: 'Paste', mnemonic: 0, iconClass: 'fa fa-paste', execute: () => { console.log('Paste'); } }); commands.addCommand('example:new-tab', { label: 'New Tab', mnemonic: 0, caption: 'Open a new tab', execute: () => { console.log('New Tab'); } }); commands.addCommand('example:close-tab', { label: 'Close Tab', mnemonic: 2, caption: 'Close the current tab', execute: () => { console.log('Close Tab'); } }); commands.addCommand('example:save-on-exit', { label: 'Save on Exit', mnemonic: 0, caption: 'Toggle the save on exit flag', execute: () => { console.log('Save on Exit'); } }); commands.addCommand('example:open-task-manager', { label: 'Task Manager', mnemonic: 5, isEnabled: () => false, execute: () => {} }); commands.addCommand('example:close', { label: 'Close', mnemonic: 0, iconClass: 'fa fa-close', execute: () => { console.log('Close'); } }); commands.addCommand('example:one', { label: 'One', execute: () => { console.log('One'); } }); commands.addCommand('example:two', { label: 'Two', execute: () => { console.log('Two'); } }); commands.addCommand('example:three', { label: 'Three', execute: () => { console.log('Three'); } }); commands.addCommand('example:four', { label: 'Four', execute: () => { console.log('Four'); } }); commands.addCommand('example:black', { label: 'Black', execute: () => { console.log('Black'); } }); commands.addCommand('example:clear-cell', { label: 'Clear Cell', execute: () => { console.log('Clear Cell'); } }); commands.addCommand('example:cut-cells', { label: 'Cut Cell(s)', execute: () => { console.log('Cut Cell(s)'); } }); commands.addCommand('example:run-cell', { label: 'Run Cell', execute: () => { console.log('Run Cell'); } }); commands.addCommand('example:cell-test', { label: 'Cell Test', execute: () => { console.log('Cell Test'); } }); commands.addCommand('notebook:new', { label: 'New Notebook', execute: () => { console.log('New Notebook'); } }); commands.addKeyBinding({ keys: ['Accel X'], selector: 'body', command: 'example:cut' }); commands.addKeyBinding({ keys: ['Accel C'], selector: 'body', command: 'example:copy' }); commands.addKeyBinding({ keys: ['Accel V'], selector: 'body', command: 'example:paste' }); commands.addKeyBinding({ keys: ['Accel J', 'Accel J'], selector: 'body', command: 'example:new-tab' }); commands.addKeyBinding({ keys: ['Accel M'], selector: 'body', command: 'example:open-task-manager' }); let menu1 = createMenu(); menu1.title.label = 'File'; menu1.title.mnemonic = 0; let menu2 = createMenu(); menu2.title.label = 'Edit'; menu2.title.mnemonic = 0; let menu3 = createMenu(); menu3.title.label = 'View'; menu3.title.mnemonic = 0; let bar = new MenuBar(); bar.addMenu(menu1); bar.addMenu(menu2); bar.addMenu(menu3); bar.id = 'menuBar'; let palette = new CommandPalette({ commands }); palette.addItem({ command: 'example:cut', category: 'Edit' }); palette.addItem({ command: 'example:copy', category: 'Edit' }); palette.addItem({ command: 'example:paste', category: 'Edit' }); palette.addItem({ command: 'example:one', category: 'Number' }); palette.addItem({ command: 'example:two', category: 'Number' }); palette.addItem({ command: 'example:three', category: 'Number' }); palette.addItem({ command: 'example:four', category: 'Number' }); palette.addItem({ command: 'example:black', category: 'Number' }); palette.addItem({ command: 'example:new-tab', category: 'File' }); palette.addItem({ command: 'example:close-tab', category: 'File' }); palette.addItem({ command: 'example:save-on-exit', category: 'File' }); palette.addItem({ command: 'example:open-task-manager', category: 'File' }); palette.addItem({ command: 'example:close', category: 'File' }); palette.addItem({ command: 'example:clear-cell', category: 'Notebook Cell Operations' }); palette.addItem({ command: 'example:cut-cells', category: 'Notebook Cell Operations' }); palette.addItem({ command: 'example:run-cell', category: 'Notebook Cell Operations' }); palette.addItem({ command: 'example:cell-test', category: 'Console' }); palette.addItem({ command: 'notebook:new', category: 'Notebook' }); palette.id = 'palette'; let contextMenu = new ContextMenu({ commands }); document.addEventListener('contextmenu', (event: MouseEvent) => { if (contextMenu.open(event)) { event.preventDefault(); } }); contextMenu.addItem({ command: 'example:cut', selector: '.content' }); contextMenu.addItem({ command: 'example:copy', selector: '.content' }); contextMenu.addItem({ command: 'example:paste', selector: '.content' }); contextMenu.addItem({ command: 'example:one', selector: '.lm-CommandPalette' }); contextMenu.addItem({ command: 'example:two', selector: '.lm-CommandPalette' }); contextMenu.addItem({ command: 'example:three', selector: '.lm-CommandPalette' }); contextMenu.addItem({ command: 'example:four', selector: '.lm-CommandPalette' }); contextMenu.addItem({ command: 'example:black', selector: '.lm-CommandPalette' }); contextMenu.addItem({ command: 'notebook:new', selector: '.lm-CommandPalette-input' }); contextMenu.addItem({ command: 'example:save-on-exit', selector: '.lm-CommandPalette-input' }); contextMenu.addItem({ command: 'example:open-task-manager', selector: '.lm-CommandPalette-input' }); contextMenu.addItem({ type: 'separator', selector: '.lm-CommandPalette-input' }); document.addEventListener('keydown', (event: KeyboardEvent) => { commands.processKeydownEvent(event); }); let r1 = new ContentWidget('Red'); let b1 = new ContentWidget('Blue'); let g1 = new ContentWidget('Green'); let y1 = new ContentWidget('Yellow'); let r2 = new ContentWidget('Red'); let b2 = new ContentWidget('Blue'); // let g2 = new ContentWidget('Green'); // let y2 = new ContentWidget('Yellow'); let dock = new DockPanel(); dock.addWidget(r1); dock.addWidget(b1, { mode: 'split-right', ref: r1 }); dock.addWidget(y1, { mode: 'split-bottom', ref: b1 }); dock.addWidget(g1, { mode: 'split-left', ref: y1 }); dock.addWidget(r2, { ref: b1 }); dock.addWidget(b2, { mode: 'split-right', ref: y1 }); dock.id = 'dock'; dock.addRequested.connect((sender: DockPanel, arg: TabBar) => { let w = new ContentWidget('Green'); sender.addWidget(w, { ref: arg.titles[0].owner }); }); let savedLayouts: DockPanel.ILayoutConfig[] = []; commands.addCommand('example:add-button', { label: 'Toggle add button', mnemonic: 0, caption: 'Toggle add Button', execute: () => { dock.addButtonEnabled = !dock.addButtonEnabled; console.log('Toggle add button'); } }); contextMenu.addItem({ command: 'example:add-button', selector: '.content' }); commands.addCommand('save-dock-layout', { label: 'Save Layout', caption: 'Save the current dock layout', execute: () => { savedLayouts.push(dock.saveLayout()); palette.addItem({ command: 'restore-dock-layout', category: 'Dock Layout', args: { index: savedLayouts.length - 1 } }); } }); commands.addCommand('restore-dock-layout', { label: args => { return `Restore Layout ${args.index as number}`; }, execute: args => { dock.restoreLayout(savedLayouts[args.index as number]); } }); palette.addItem({ command: 'save-dock-layout', category: 'Dock Layout', rank: 0 }); BoxPanel.setStretch(dock, 1); let main = new BoxPanel({ direction: 'left-to-right', spacing: 0 }); main.id = 'main'; main.addWidget(palette); main.addWidget(dock); window.onresize = () => { main.update(); }; Widget.attach(bar, document.body); Widget.attach(main, document.body); } window.onload = main; lumino-2021.12.13/examples/example-dockpanel/style/000077500000000000000000000000001415564225700217705ustar00rootroot00000000000000lumino-2021.12.13/examples/example-dockpanel/style/content.css000066400000000000000000000016051415564225700241560ustar00rootroot00000000000000/*----------------------------------------------------------------------------- | Copyright (c) Jupyter Development Team. | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ .content { min-width: 50px; min-height: 50px; display: flex; flex-direction: column; padding: 8px; border: 1px solid #c0c0c0; border-top: none; background: white; box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.2); } .content > div { flex: 1 1 auto; border: 1px solid #505050; overflow: auto; } .content input { margin: 8px; } .red > div { background: #e74c3c; } .yellow > div { background: #f1c40f; } .green > div { background: #27ae60; } .blue > div { background: #3498db; } lumino-2021.12.13/examples/example-dockpanel/style/index.css000066400000000000000000000013551415564225700236150ustar00rootroot00000000000000/*----------------------------------------------------------------------------- | Copyright (c) 2014-2018, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ @import '~@lumino/default-theme/style/index.css'; @import './content.css'; body { display: flex; flex-direction: column; position: absolute; top: 0; left: 0; right: 0; bottom: 0; margin: 0; padding: 0; overflow: hidden; } #menuBar { flex: 0 0 auto; } #main { flex: 1 1 auto; } #palette { min-width: 300px; border-right: 1px solid #dddddd; } #dock { padding: 4px; } lumino-2021.12.13/examples/example-dockpanel/tsconfig.json000066400000000000000000000006201415564225700233350ustar00rootroot00000000000000{ "compilerOptions": { "declaration": false, "noImplicitAny": true, "noEmitOnError": true, "noUnusedLocals": true, "strictNullChecks": true, "sourceMap": true, "module": "commonjs", "moduleResolution": "node", "target": "ES5", "outDir": "./build", "lib": ["ES5", "ES2015.Promise", "ES2015.Iterable", "DOM"], "types": [] }, "include": ["src/*"] } lumino-2021.12.13/examples/example-dockpanel/webpack.config.js000066400000000000000000000007041415564225700240470ustar00rootroot00000000000000var path = require('path'); module.exports = { entry: './build/index.js', mode: 'development', devtool: 'source-map', output: { path: __dirname + '/build/', filename: 'bundle.example.js', publicPath: './build/' }, module: { rules: [ { test: /\.js$/, use: ['source-map-loader'], enforce: 'pre' }, { test: /\.css$/, use: ['style-loader', 'css-loader'] }, { test: /\.png$/, use: 'file-loader' } ] } }; lumino-2021.12.13/lerna.json000066400000000000000000000001441415564225700154140ustar00rootroot00000000000000{ "lerna": "3.14.1", "version": "independent", "npmClient": "yarn", "useWorkspaces": true } lumino-2021.12.13/lint-staged.config.js000066400000000000000000000014021415564225700174330ustar00rootroot00000000000000const escape = require('shell-quote').quote; const fs = require('fs'); const isWin = process.platform === 'win32'; const escapeFileNames = filenames => filenames .filter(filename => fs.existsSync(filename)) .map(filename => `"${isWin ? filename : escape([filename])}"`) .join(' '); module.exports = { '**/*{.css,.json,.md}': filenames => { const escapedFileNames = escapeFileNames(filenames); return [ `prettier --write ${escapedFileNames}`, `git add -f ${escapedFileNames}` ]; }, '**/*{.ts,.js}': filenames => { const escapedFileNames = escapeFileNames(filenames); return [ `prettier --write ${escapedFileNames}`, `eslint --fix ${escapedFileNames}`, `git add -f ${escapedFileNames}` ]; } }; lumino-2021.12.13/notebooks/000077500000000000000000000000001415564225700154245ustar00rootroot00000000000000lumino-2021.12.13/notebooks/.babelrc000066400000000000000000000004711415564225700170210ustar00rootroot00000000000000{ "presets": [ "@babel/env", [ "@babel/typescript", { "isTSX": true, "allExtensions": true, "allowNamespaces": true } ], "@babel/react" ], "plugins": [ ["@babel/plugin-transform-modules-commonjs", { "strictMode": false }] ] } lumino-2021.12.13/notebooks/IdGeneration.ipynb000066400000000000000000000267061415564225700210520ustar00rootroot00000000000000{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Testing different schemes for encoding ids" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Imports" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "const convertHrtime = require('convert-hrtime');" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "const d3array = require('d3-array')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Timing code" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "export function timeit(n: number, f: any, args: any[]) {\n", " let sum = 0.0;\n", " for (i=0; i> 3;\n", " }\n", "\n", " export\n", " function idPathAt(id: string, i: number): number {\n", " let j = i << 3;\n", " let a = id.charCodeAt(j + 0);\n", " let b = id.charCodeAt(j + 1);\n", " let c = id.charCodeAt(j + 2);\n", " return a * 0x100000000 + b * 0x10000 + c;\n", " }\n", "\n", " export\n", " function idVersionAt(id: string, i: number): number {\n", " let j = i << 3;\n", " let a = id.charCodeAt(j + 3);\n", " let b = id.charCodeAt(j + 4);\n", " let c = id.charCodeAt(j + 5);\n", " return a * 0x100000000 + b * 0x10000 + c;\n", " }\n", "\n", " export\n", " function idStoreAt(id: string, i: number): number {\n", " let j = i << 3;\n", " let a = id.charCodeAt(j + 6);\n", " let b = id.charCodeAt(j + 7);\n", " return a * 0x10000 + b;\n", " }\n", "\n", " export\n", " function randomPath(min: number, max: number): number {\n", " return min + Math.round(Math.random() * Math.sqrt(max - min));\n", " }\n", "}" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "export\n", "function createDuplexId(version: number, store: number): string {\n", " // Split the version into 16-bit values.\n", " let vc = version & 0xFFFF;\n", " let vb = (((version - vc) / 0x10000) | 0) & 0xFFFF;\n", " let va = (((version - vb - vc) / 0x100000000) | 0) & 0xFFFF;\n", "\n", " // Split the store id into 16-bit values.\n", " let sb = store & 0xFFFF;\n", " let sa = (((store - sb) / 0x10000) | 0) & 0xFFFF;\n", "\n", " // Convert the parts into a string identifier duplex.\n", " return String.fromCharCode(va, vb, vc, sa, sb);\n", "}" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "export\n", "function createTriplexId(version: number, store: number, lower: string, upper: string): string {\n", " // The maximum path in a triplex id.\n", " const MAX_PATH = 0xFFFFFFFFFFFF;\n", "\n", " // Set up the variable to hold the id.\n", " let id = '';\n", "\n", " // Fetch the triplet counts of the ids.\n", " let lowerCount = lower ? Private.idTripletCount(lower) : 0;\n", " let upperCount = upper ? Private.idTripletCount(upper) : 0;\n", "\n", " // Iterate over the id triplets.\n", " for (let i = 0, n = Math.max(lowerCount, upperCount); i < n; ++i) {\n", " // Fetch the lower identifier triplet, padding as needed.\n", " let lp: number;\n", " let lc: number;\n", " let ls: number;\n", " if (i >= lowerCount) {\n", " lp = 0;\n", " lc = 0;\n", " ls = 0;\n", " } else {\n", " lp = Private.idPathAt(lower, i);\n", " lc = Private.idVersionAt(lower, i);\n", " ls = Private.idStoreAt(lower, i);\n", " }\n", "\n", " // Fetch the upper identifier triplet, padding as needed.\n", " let up: number;\n", " let uc: number;\n", " let us: number;\n", " if (i >= upperCount) {\n", " up = upperCount === 0 ? MAX_PATH + 1 : 0;\n", " uc = 0;\n", " us = 0;\n", " } else {\n", " up = Private.idPathAt(upper, i);\n", " uc = Private.idVersionAt(upper, i);\n", " us = Private.idStoreAt(upper, i);\n", " }\n", "\n", " // If the triplets are the same, copy the triplet and continue.\n", " if (lp === up && lc === uc && ls === us) {\n", " id += Private.createTriplet(lp, lc, ls);\n", " continue;\n", " }\n", "\n", " // If the triplets are different, the well-ordered identifiers\n", " // assumption means that the lower triplet compares less than\n", " // the upper triplet. The task now is to find the nearest free\n", " // path slot among the remaining triplets.\n", "\n", " // If there is free space between the path portions of the\n", " // triplets, select a new path which falls between them.\n", " if (up - lp > 1) {\n", " let np = Private.randomPath(lp + 1, up - 1);\n", " id += Private.createTriplet(np, version, store);\n", " return id.slice();\n", " }\n", "\n", " // Otherwise, copy the left triplet and reset the upper count\n", " // to zero so that the loop chooses the nearest available path\n", " // slot after the current lower triplet.\n", " id += Private.createTriplet(lp, lc, ls);\n", " upperCount = 0;\n", " }\n", "\n", " // If this point is reached, the lower and upper identifiers share\n", " // the same path but diverge based on the version or store id. It is\n", " // safe to insert anywhere in an extra triplet.\n", " let np = Private.randomPath(1, MAX_PATH);\n", " id += Private.createTriplet(np, version, store);\n", " return id.slice();\n", "}" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "export\n", "function createTriplexIds(n: number, version: number, store: number, lower: string, upper: string): string[] {\n", " let ids: string[] = [];\n", "\n", " while (ids.length < n) {\n", " let id = createTriplexId(version, store, lower, upper);\n", " ids.push(id);\n", " lower = id;\n", " }\n", "\n", " return ids;\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Base64 encoding" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "export function encodeBase64(input: string): string {\n", " const buffer = Buffer.from(input);\n", " return buffer.toString('base64');\n", "}" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "export function decodeBase64(input: string): string {\n", " return Buffer.from(input, 'base64').toString()\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Regular expression (Ian's PR)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "const HS_L = '\\uD800';\n", "const HS_U = '\\uDBFF';\n", "const LS_L = '\\uDC00';\n", "const LS_U = '\\uDFFF';\n", "const LS_REGEX = new RegExp(`([${LS_L}-${LS_U}])`, 'g');\n", "const UNPAIRED_HS_REGEX = new RegExp(\n", " `([${HS_L}-${HS_U}])(?![${LS_L}-${LS_U}])`,\n", " 'g',\n", ");\n", "const PAIRED_LS_REGEX = new RegExp(`X${HS_L}([${LS_L}-${LS_U}])`, 'g');\n", "const PAIRED_HS_REGEX = new RegExp(`([${HS_L}-${HS_U}])${LS_L}X`, 'g');\n", "\n", "export\n", "function stripSurrogates(id: string): string {\n", " return id.replace(PAIRED_LS_REGEX, '$1').replace(PAIRED_HS_REGEX, '$1');\n", "}\n", "\n", "export\n", "function generateIdString(str: string): string {\n", " str = str.replace(LS_REGEX, `X${HS_L}$1`);\n", " str = str.replace(UNPAIRED_HS_REGEX, `$1${LS_L}X`);\n", " return str;\n", "}\n", "\n", "export\n", "function stringToCharCodes(s: string): number[] {\n", " result = new Array();\n", " for (i=0; i {\n", " return timeit(100, encodeBase64, [item])\n", "})" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "meanA = d3array.mean(timesA)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "timesB = ids.map(item => {\n", " return timeit(100, generateIdString, [item])\n", "})" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "meanB = d3array.mean(timesB)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "meanB/meanA" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "patchIds = createTriplexIds(2**13, 1, 1, ids[0], ids[1])" ] } ], "metadata": { "kernelspec": { "display_name": "jp-Babel (Node.js)", "language": "babel", "name": "babel" }, "language_info": { "file_extension": ".js", "mimetype": "application/javascript", "name": "javascript", "version": "12.12.0" } }, "nbformat": 4, "nbformat_minor": 4 } lumino-2021.12.13/notebooks/README.md000066400000000000000000000013721415564225700167060ustar00rootroot00000000000000# Jupyter Notebooks with TypeScript This directory contains a simple configuration and documentation for using the Jupyter notebook with TypeScript using the [jp-babel](https://github.com/n-riesco/jp-babel) kernel. This kernel uses [Babel](https://babeljs.io/) to transpile TypeScript code in a notebook to a form that can be run by nodejs. Runtime typechecking is not done. ## Installation and configuration 1. Install the `jp-babel` npm package as a global npm package: ``` npm install -g jp-babel ``` 2. Use the `jp-babel` command line to install the kernel: ``` jp-babel-install ``` 3. Run `npm install` in this directory to install other dependencies: ``` npm install ``` At this point you should be able to launch JupyterLab: ``` jupyter lab ``` lumino-2021.12.13/notebooks/package.json000066400000000000000000000012621415564225700177130ustar00rootroot00000000000000{ "name": "lumino_notebooks", "version": "0.1", "description": "A set of JavaScript notebooks for lumino.", "keywords": [], "author": "Project Jupyter Contributors", "license": "Revised BSD", "dependencies": { "convert-hrtime": "^3.0.0", "d3-array": "^2.4.0", "lodash": "^4.17.15", "react": "^16.11.0", "react-dom": "^16.11.0" }, "devDependencies": { "@babel/core": "^7.7.2", "@babel/plugin-transform-modules-commonjs": "^7.7.0", "@babel/preset-env": "^7.7.1", "@babel/preset-react": "^7.0.0", "@babel/preset-typescript": "^7.7.2", "@types/react": "^16.9.11", "@types/react-dom": "^16.9.4", "typescript": "^3.7.2" } } lumino-2021.12.13/package.json000066400000000000000000000060001415564225700157030ustar00rootroot00000000000000{ "name": "lumino-top-level", "version": "2021.12.13", "private": true, "workspaces": [ "examples/*", "packages/*", "tests/*" ], "scripts": { "api": "lerna run api", "build": "npm run build:src", "build:dist": "npm run build && npm run minimize", "build:examples": "lerna run build --scope \"@lumino/example-*\" --concurrency 1", "build:src": "lerna run build --scope \"@lumino/!(test-|example-)*\" --concurrency 1", "build:test": "lerna run build:test", "clean": "lerna run clean", "clean:examples": "lerna run clean --scope \"@lumino/example-*\"", "clean:src": "lerna run clean --scope \"@lumino/!(test-|example-)*\"", "clean:tests": "lerna run clean:tests", "docs": "rimraf docs/source/api && lerna run build:src --concurrency 1 && lerna run docs", "eslint": "eslint --ext .js,.jsx,.ts,.tsx --cache --fix .", "eslint:check": "eslint --ext .js,.jsx,.ts,.tsx --cache .", "get:dependency": "get-dependency", "lint": "yarn && yarn run prettier && yarn run eslint", "lint:check": "yarn run prettier:check && yarn run eslint:check", "minimize": "lerna run minimize", "prettier": "prettier --write \"**/*{.ts,.tsx,.js,.jsx,.css,.json,.md}\"", "prettier:check": "prettier --check \"**/*{.ts,.tsx,.js,.jsx,.css,.json,.md}\"", "publish": "npm run clean && npm run build:dist && node scripts/tag-versions.js && lerna publish --yes -m \"Publish\" from-package", "remove:dependency": "remove-dependency", "test": "lerna run test --scope \"@lumino/!(example-)*\"", "test:chrome": "lerna run test:chrome", "test:chrome-headless": "lerna run test:chrome-headless", "test:examples": "lerna run test --scope \"@lumino/example-*\"", "test:firefox": "lerna run test:firefox", "test:firefox-headless": "lerna run test:firefox-headless", "test:ie": "lerna run test:ie --concurrency 1", "update:dependency": "update-dependency --lerna", "update:versions": "lerna version --no-push --no-git-tag-version" }, "dependencies": {}, "devDependencies": { "@jupyterlab/buildutils": "^3.0.0", "@typescript-eslint/eslint-plugin": "~4.8.1", "@typescript-eslint/parser": "~4.8.1", "eslint": "~7.14.0", "eslint-config-prettier": "~6.15.0", "eslint-plugin-prettier": "~3.1.4", "husky": "^4.2.5", "lerna": "^4.0.0", "lint-staged": "^10.2.13", "prettier": "~2.1.1", "shell-quote": "^1.7.2", "typedoc": "~0.15.0" }, "jupyter-releaser": { "skip": [ "check-links" ], "options": { "ignore-links": [ "./api/index.html", "examples/accordionpanel/index.html", "examples/datagrid/index.html", "examples/dockpanel/index.html" ] }, "hooks": { "after-build-changelog": [ "yarn build:dist", "node scripts/format-changelog.js" ], "before-build-npm": "yarn build:dist", "before-draft-release": "node scripts/tag-versions.js" } }, "husky": { "hooks": { "pre-commit": "lint-staged" } } } lumino-2021.12.13/packages/000077500000000000000000000000001415564225700151775ustar00rootroot00000000000000lumino-2021.12.13/packages/algorithm/000077500000000000000000000000001415564225700171655ustar00rootroot00000000000000lumino-2021.12.13/packages/algorithm/api-extractor.json000066400000000000000000000014611415564225700226440ustar00rootroot00000000000000/** * Config file for API Extractor. For more info, please visit: https://api-extractor.com */ { "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", /** * Optionally specifies another JSON config file that this file extends from. This provides a way for * standard settings to be shared across multiple projects. * * If the path starts with "./" or "../", the path is resolved relative to the folder of the file that contains * the "extends" field. Otherwise, the first path segment is interpreted as an NPM package name, and will be * resolved using NodeJS require(). * * SUPPORTED TOKENS: none * DEFAULT VALUE: "" */ "extends": "../../api-extractor-base.json" // "extends": "my-package/include/api-extractor-base.json" } lumino-2021.12.13/packages/algorithm/package.json000066400000000000000000000047231415564225700214610ustar00rootroot00000000000000{ "name": "@lumino/algorithm", "version": "1.9.1", "description": "Lumino Algorithms and Iterators", "homepage": "https://github.com/jupyterlab/lumino", "bugs": { "url": "https://github.com/jupyterlab/lumino/issues" }, "repository": { "type": "git", "url": "https://github.com/jupyterlab/lumino.git" }, "license": "BSD-3-Clause", "author": "S. Chris Colbert ", "contributors": [ "A. Darian ", "Dave Willmer ", "S. Chris Colbert ", "Steven Silvester " ], "main": "dist/index.js", "jsdelivr": "dist/index.min.js", "unpkg": "dist/index.min.js", "module": "dist/index.es6", "types": "types/index.d.ts", "files": [ "dist/*", "src/*", "types/*" ], "scripts": { "api": "api-extractor run --local --verbose", "build": "npm run build:src && rollup -c", "build:src": "tsc --build", "build:test": "tsc --build tests && cd tests && webpack", "clean": "rimraf ./lib && rimraf *.tsbuildinfo && rimraf ./types && rimraf ./dist", "clean:test": "rimraf tests/build", "docs": "typedoc --options tdoptions.json src", "minimize": "terser dist/index.js -c -m --source-map \"content='dist/index.js.map',url='index.min.js.map'\" -o dist/index.min.js", "test": "npm run test:firefox-headless", "test:chrome": "cd tests && karma start --browsers=Chrome", "test:chrome-headless": "cd tests && karma start --browsers=ChromeHeadless", "test:firefox": "cd tests && karma start --browsers=Firefox", "test:firefox-headless": "cd tests && karma start --browsers=FirefoxHeadless", "test:ie": "cd tests && karma start --browsers=IE", "watch": "tsc --build --watch" }, "devDependencies": { "@microsoft/api-extractor": "^7.6.0", "@types/mocha": "^2.2.39", "chai": "^4.3.4", "karma": "^6.3.4", "karma-chrome-launcher": "^3.1.0", "karma-firefox-launcher": "^2.1.1", "karma-ie-launcher": "^1.0.0", "karma-mocha": "^2.0.1", "karma-mocha-reporter": "^2.2.5", "mocha": "^9.0.3", "rimraf": "^3.0.2", "rollup": "^2.56.0", "rollup-plugin-node-resolve": "^5.2.0", "rollup-plugin-postcss": "^4.0.0", "rollup-plugin-sourcemaps": "^0.6.3", "terser": "^5.7.1", "tslib": "^2.3.0", "typedoc": "~0.15.0", "typescript": "~3.6.0", "webpack": "^4.41.3", "webpack-cli": "^3.3.10" }, "publishConfig": { "access": "public" } } lumino-2021.12.13/packages/algorithm/rollup.config.js000066400000000000000000000015231415564225700223050ustar00rootroot00000000000000import nodeResolve from 'rollup-plugin-node-resolve'; import sourcemaps from 'rollup-plugin-sourcemaps'; import postcss from 'rollup-plugin-postcss'; const pkg = require('./package.json'); const globals = id => id.indexOf('@lumino/') === 0 ? id.replace('@lumino/', 'lumino_') : id; export default [ { input: 'lib/index', external: id => pkg.dependencies && !!pkg.dependencies[id], output: [ { file: pkg.main, globals, format: 'umd', sourcemap: true, name: pkg.name }, { file: pkg.module + '.js', format: 'es', sourcemap: true, name: pkg.name } ], plugins: [ nodeResolve({ preferBuiltins: true }), sourcemaps(), postcss({ extensions: ['.css'], minimize: true }) ] } ]; lumino-2021.12.13/packages/algorithm/src/000077500000000000000000000000001415564225700177545ustar00rootroot00000000000000lumino-2021.12.13/packages/algorithm/src/array.ts000066400000000000000000001230371415564225700214500ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ /** * The namespace for array-specific algorithms. */ export namespace ArrayExt { /** * Find the index of the first occurrence of a value in an array. * * @param array - The array-like object to search. * * @param value - The value to locate in the array. Values are * compared using strict `===` equality. * * @param start - The index of the first element in the range to be * searched, inclusive. The default value is `0`. Negative values * are taken as an offset from the end of the array. * * @param stop - The index of the last element in the range to be * searched, inclusive. The default value is `-1`. Negative values * are taken as an offset from the end of the array. * * @returns The index of the first occurrence of the value, or `-1` * if the value is not found. * * #### Notes * If `stop < start` the search will wrap at the end of the array. * * #### Complexity * Linear. * * #### Undefined Behavior * A `start` or `stop` which is non-integral. * * #### Example * ```typescript * import { ArrayExt } from '@lumino/algorithm'; * * let data = ['one', 'two', 'three', 'four', 'one']; * ArrayExt.firstIndexOf(data, 'red'); // -1 * ArrayExt.firstIndexOf(data, 'one'); // 0 * ArrayExt.firstIndexOf(data, 'one', 1); // 4 * ArrayExt.firstIndexOf(data, 'two', 2); // -1 * ArrayExt.firstIndexOf(data, 'two', 2, 1); // 1 * ``` */ export function firstIndexOf( array: ArrayLike, value: T, start = 0, stop = -1 ): number { let n = array.length; if (n === 0) { return -1; } if (start < 0) { start = Math.max(0, start + n); } else { start = Math.min(start, n - 1); } if (stop < 0) { stop = Math.max(0, stop + n); } else { stop = Math.min(stop, n - 1); } let span: number; if (stop < start) { span = stop + 1 + (n - start); } else { span = stop - start + 1; } for (let i = 0; i < span; ++i) { let j = (start + i) % n; if (array[j] === value) { return j; } } return -1; } /** * Find the index of the last occurrence of a value in an array. * * @param array - The array-like object to search. * * @param value - The value to locate in the array. Values are * compared using strict `===` equality. * * @param start - The index of the first element in the range to be * searched, inclusive. The default value is `-1`. Negative values * are taken as an offset from the end of the array. * * @param stop - The index of the last element in the range to be * searched, inclusive. The default value is `0`. Negative values * are taken as an offset from the end of the array. * * @returns The index of the last occurrence of the value, or `-1` * if the value is not found. * * #### Notes * If `start < stop` the search will wrap at the front of the array. * * #### Complexity * Linear. * * #### Undefined Behavior * A `start` or `stop` which is non-integral. * * #### Example * ```typescript * import { ArrayExt } from '@lumino/algorithm'; * * let data = ['one', 'two', 'three', 'four', 'one']; * ArrayExt.lastIndexOf(data, 'red'); // -1 * ArrayExt.lastIndexOf(data, 'one'); // 4 * ArrayExt.lastIndexOf(data, 'one', 1); // 0 * ArrayExt.lastIndexOf(data, 'two', 0); // -1 * ArrayExt.lastIndexOf(data, 'two', 0, 1); // 1 * ``` */ export function lastIndexOf( array: ArrayLike, value: T, start = -1, stop = 0 ): number { let n = array.length; if (n === 0) { return -1; } if (start < 0) { start = Math.max(0, start + n); } else { start = Math.min(start, n - 1); } if (stop < 0) { stop = Math.max(0, stop + n); } else { stop = Math.min(stop, n - 1); } let span: number; if (start < stop) { span = start + 1 + (n - stop); } else { span = start - stop + 1; } for (let i = 0; i < span; ++i) { let j = (start - i + n) % n; if (array[j] === value) { return j; } } return -1; } /** * Find the index of the first value which matches a predicate. * * @param array - The array-like object to search. * * @param fn - The predicate function to apply to the values. * * @param start - The index of the first element in the range to be * searched, inclusive. The default value is `0`. Negative values * are taken as an offset from the end of the array. * * @param stop - The index of the last element in the range to be * searched, inclusive. The default value is `-1`. Negative values * are taken as an offset from the end of the array. * * @returns The index of the first matching value, or `-1` if no * matching value is found. * * #### Notes * If `stop < start` the search will wrap at the end of the array. * * #### Complexity * Linear. * * #### Undefined Behavior * A `start` or `stop` which is non-integral. * * Modifying the length of the array while searching. * * #### Example * ```typescript * import { ArrayExt } from '@lumino/algorithm'; * * function isEven(value: number): boolean { * return value % 2 === 0; * } * * let data = [1, 2, 3, 4, 3, 2, 1]; * ArrayExt.findFirstIndex(data, isEven); // 1 * ArrayExt.findFirstIndex(data, isEven, 4); // 5 * ArrayExt.findFirstIndex(data, isEven, 6); // -1 * ArrayExt.findFirstIndex(data, isEven, 6, 5); // 1 * ``` */ export function findFirstIndex( array: ArrayLike, fn: (value: T, index: number) => boolean, start = 0, stop = -1 ): number { let n = array.length; if (n === 0) { return -1; } if (start < 0) { start = Math.max(0, start + n); } else { start = Math.min(start, n - 1); } if (stop < 0) { stop = Math.max(0, stop + n); } else { stop = Math.min(stop, n - 1); } let span: number; if (stop < start) { span = stop + 1 + (n - start); } else { span = stop - start + 1; } for (let i = 0; i < span; ++i) { let j = (start + i) % n; if (fn(array[j], j)) { return j; } } return -1; } /** * Find the index of the last value which matches a predicate. * * @param object - The array-like object to search. * * @param fn - The predicate function to apply to the values. * * @param start - The index of the first element in the range to be * searched, inclusive. The default value is `-1`. Negative values * are taken as an offset from the end of the array. * * @param stop - The index of the last element in the range to be * searched, inclusive. The default value is `0`. Negative values * are taken as an offset from the end of the array. * * @returns The index of the last matching value, or `-1` if no * matching value is found. * * #### Notes * If `start < stop` the search will wrap at the front of the array. * * #### Complexity * Linear. * * #### Undefined Behavior * A `start` or `stop` which is non-integral. * * Modifying the length of the array while searching. * * #### Example * ```typescript * import { ArrayExt } from '@lumino/algorithm'; * * function isEven(value: number): boolean { * return value % 2 === 0; * } * * let data = [1, 2, 3, 4, 3, 2, 1]; * ArrayExt.findLastIndex(data, isEven); // 5 * ArrayExt.findLastIndex(data, isEven, 4); // 3 * ArrayExt.findLastIndex(data, isEven, 0); // -1 * ArrayExt.findLastIndex(data, isEven, 0, 1); // 5 * ``` */ export function findLastIndex( array: ArrayLike, fn: (value: T, index: number) => boolean, start = -1, stop = 0 ): number { let n = array.length; if (n === 0) { return -1; } if (start < 0) { start = Math.max(0, start + n); } else { start = Math.min(start, n - 1); } if (stop < 0) { stop = Math.max(0, stop + n); } else { stop = Math.min(stop, n - 1); } let d: number; if (start < stop) { d = start + 1 + (n - stop); } else { d = start - stop + 1; } for (let i = 0; i < d; ++i) { let j = (start - i + n) % n; if (fn(array[j], j)) { return j; } } return -1; } /** * Find the first value which matches a predicate. * * @param array - The array-like object to search. * * @param fn - The predicate function to apply to the values. * * @param start - The index of the first element in the range to be * searched, inclusive. The default value is `0`. Negative values * are taken as an offset from the end of the array. * * @param stop - The index of the last element in the range to be * searched, inclusive. The default value is `-1`. Negative values * are taken as an offset from the end of the array. * * @returns The first matching value, or `undefined` if no matching * value is found. * * #### Notes * If `stop < start` the search will wrap at the end of the array. * * #### Complexity * Linear. * * #### Undefined Behavior * A `start` or `stop` which is non-integral. * * Modifying the length of the array while searching. * * #### Example * ```typescript * import { ArrayExt } from '@lumino/algorithm'; * * function isEven(value: number): boolean { * return value % 2 === 0; * } * * let data = [1, 2, 3, 4, 3, 2, 1]; * ArrayExt.findFirstValue(data, isEven); // 2 * ArrayExt.findFirstValue(data, isEven, 2); // 4 * ArrayExt.findFirstValue(data, isEven, 6); // undefined * ArrayExt.findFirstValue(data, isEven, 6, 5); // 2 * ``` */ export function findFirstValue( array: ArrayLike, fn: (value: T, index: number) => boolean, start = 0, stop = -1 ): T | undefined { let index = findFirstIndex(array, fn, start, stop); return index !== -1 ? array[index] : undefined; } /** * Find the last value which matches a predicate. * * @param object - The array-like object to search. * * @param fn - The predicate function to apply to the values. * * @param start - The index of the first element in the range to be * searched, inclusive. The default value is `-1`. Negative values * are taken as an offset from the end of the array. * * @param stop - The index of the last element in the range to be * searched, inclusive. The default value is `0`. Negative values * are taken as an offset from the end of the array. * * @returns The last matching value, or `undefined` if no matching * value is found. * * #### Notes * If `start < stop` the search will wrap at the front of the array. * * #### Complexity * Linear. * * #### Undefined Behavior * A `start` or `stop` which is non-integral. * * Modifying the length of the array while searching. * * #### Example * ```typescript * import { ArrayExt } from '@lumino/algorithm'; * * function isEven(value: number): boolean { * return value % 2 === 0; * } * * let data = [1, 2, 3, 4, 3, 2, 1]; * ArrayExt.findLastValue(data, isEven); // 2 * ArrayExt.findLastValue(data, isEven, 4); // 4 * ArrayExt.findLastValue(data, isEven, 0); // undefined * ArrayExt.findLastValue(data, isEven, 0, 1); // 2 * ``` */ export function findLastValue( array: ArrayLike, fn: (value: T, index: number) => boolean, start = -1, stop = 0 ): T | undefined { let index = findLastIndex(array, fn, start, stop); return index !== -1 ? array[index] : undefined; } /** * Find the index of the first element which compares `>=` to a value. * * @param array - The sorted array-like object to search. * * @param value - The value to locate in the array. * * @param fn - The 3-way comparison function to apply to the values. * It should return `< 0` if an element is less than a value, `0` if * an element is equal to a value, or `> 0` if an element is greater * than a value. * * @param start - The index of the first element in the range to be * searched, inclusive. The default value is `0`. Negative values * are taken as an offset from the end of the array. * * @param stop - The index of the last element in the range to be * searched, inclusive. The default value is `-1`. Negative values * are taken as an offset from the end of the array. * * @returns The index of the first element which compares `>=` to the * value, or `length` if there is no such element. If the computed * index for `stop` is less than `start`, then the computed index * for `start` is returned. * * #### Notes * The array must already be sorted in ascending order according to * the comparison function. * * #### Complexity * Logarithmic. * * #### Undefined Behavior * Searching a range which is not sorted in ascending order. * * A `start` or `stop` which is non-integral. * * Modifying the length of the array while searching. * * #### Example * ```typescript * import { ArrayExt } from '@lumino/algorithm'; * * function numberCmp(a: number, b: number): number { * return a - b; * } * * let data = [0, 3, 4, 7, 7, 9]; * ArrayExt.lowerBound(data, 0, numberCmp); // 0 * ArrayExt.lowerBound(data, 6, numberCmp); // 3 * ArrayExt.lowerBound(data, 7, numberCmp); // 3 * ArrayExt.lowerBound(data, -1, numberCmp); // 0 * ArrayExt.lowerBound(data, 10, numberCmp); // 6 * ``` */ export function lowerBound( array: ArrayLike, value: U, fn: (element: T, value: U) => number, start = 0, stop = -1 ): number { let n = array.length; if (n === 0) { return 0; } if (start < 0) { start = Math.max(0, start + n); } else { start = Math.min(start, n - 1); } if (stop < 0) { stop = Math.max(0, stop + n); } else { stop = Math.min(stop, n - 1); } let begin = start; let span = stop - start + 1; while (span > 0) { let half = span >> 1; let middle = begin + half; if (fn(array[middle], value) < 0) { begin = middle + 1; span -= half + 1; } else { span = half; } } return begin; } /** * Find the index of the first element which compares `>` than a value. * * @param array - The sorted array-like object to search. * * @param value - The value to locate in the array. * * @param fn - The 3-way comparison function to apply to the values. * It should return `< 0` if an element is less than a value, `0` if * an element is equal to a value, or `> 0` if an element is greater * than a value. * * @param start - The index of the first element in the range to be * searched, inclusive. The default value is `0`. Negative values * are taken as an offset from the end of the array. * * @param stop - The index of the last element in the range to be * searched, inclusive. The default value is `-1`. Negative values * are taken as an offset from the end of the array. * * @returns The index of the first element which compares `>` than the * value, or `length` if there is no such element. If the computed * index for `stop` is less than `start`, then the computed index * for `start` is returned. * * #### Notes * The array must already be sorted in ascending order according to * the comparison function. * * #### Complexity * Logarithmic. * * #### Undefined Behavior * Searching a range which is not sorted in ascending order. * * A `start` or `stop` which is non-integral. * * Modifying the length of the array while searching. * * #### Example * ```typescript * import { ArrayExt } from '@lumino/algorithm'; * * function numberCmp(a: number, b: number): number { * return a - b; * } * * let data = [0, 3, 4, 7, 7, 9]; * ArrayExt.upperBound(data, 0, numberCmp); // 1 * ArrayExt.upperBound(data, 6, numberCmp); // 3 * ArrayExt.upperBound(data, 7, numberCmp); // 5 * ArrayExt.upperBound(data, -1, numberCmp); // 0 * ArrayExt.upperBound(data, 10, numberCmp); // 6 * ``` */ export function upperBound( array: ArrayLike, value: U, fn: (element: T, value: U) => number, start = 0, stop = -1 ): number { let n = array.length; if (n === 0) { return 0; } if (start < 0) { start = Math.max(0, start + n); } else { start = Math.min(start, n - 1); } if (stop < 0) { stop = Math.max(0, stop + n); } else { stop = Math.min(stop, n - 1); } let begin = start; let span = stop - start + 1; while (span > 0) { let half = span >> 1; let middle = begin + half; if (fn(array[middle], value) > 0) { span = half; } else { begin = middle + 1; span -= half + 1; } } return begin; } /** * Test whether two arrays are shallowly equal. * * @param a - The first array-like object to compare. * * @param b - The second array-like object to compare. * * @param fn - The comparison function to apply to the elements. It * should return `true` if the elements are "equal". The default * compares elements using strict `===` equality. * * @returns Whether the two arrays are shallowly equal. * * #### Complexity * Linear. * * #### Undefined Behavior * Modifying the length of the arrays while comparing. * * #### Example * ```typescript * import { ArrayExt } from '@lumino/algorithm'; * * let d1 = [0, 3, 4, 7, 7, 9]; * let d2 = [0, 3, 4, 7, 7, 9]; * let d3 = [42]; * ArrayExt.shallowEqual(d1, d2); // true * ArrayExt.shallowEqual(d2, d3); // false * ``` */ export function shallowEqual( a: ArrayLike, b: ArrayLike, fn?: (a: T, b: T) => boolean ): boolean { // Check for object identity first. if (a === b) { return true; } // Bail early if the lengths are different. if (a.length !== b.length) { return false; } // Compare each element for equality. for (let i = 0, n = a.length; i < n; ++i) { if (fn ? !fn(a[i], b[i]) : a[i] !== b[i]) { return false; } } // The array are shallowly equal. return true; } /** * Create a slice of an array subject to an optional step. * * @param array - The array-like object of interest. * * @param options - The options for configuring the slice. * * @returns A new array with the specified values. * * @throws An exception if the slice `step` is `0`. * * #### Complexity * Linear. * * #### Undefined Behavior * A `start`, `stop`, or `step` which is non-integral. * * #### Example * ```typescript * import { ArrayExt } from '@lumino/algorithm'; * * let data = [0, 3, 4, 7, 7, 9]; * ArrayExt.slice(data); // [0, 3, 4, 7, 7, 9] * ArrayExt.slice(data, { start: 2 }); // [4, 7, 7, 9] * ArrayExt.slice(data, { start: 0, stop: 4 }); // [0, 3, 4, 7] * ArrayExt.slice(data, { step: 2 }); // [0, 4, 7] * ArrayExt.slice(data, { step: -1 }); // [9, 7, 7, 4, 3, 0] * ``` */ export function slice( array: ArrayLike, options: slice.IOptions = {} ): T[] { // Extract the options. let { start, stop, step } = options; // Set up the `step` value. if (step === undefined) { step = 1; } // Validate the step size. if (step === 0) { throw new Error('Slice `step` cannot be zero.'); } // Look up the length of the array. let n = array.length; // Set up the `start` value. if (start === undefined) { start = step < 0 ? n - 1 : 0; } else if (start < 0) { start = Math.max(start + n, step < 0 ? -1 : 0); } else if (start >= n) { start = step < 0 ? n - 1 : n; } // Set up the `stop` value. if (stop === undefined) { stop = step < 0 ? -1 : n; } else if (stop < 0) { stop = Math.max(stop + n, step < 0 ? -1 : 0); } else if (stop >= n) { stop = step < 0 ? n - 1 : n; } // Compute the slice length. let length; if ((step < 0 && stop >= start) || (step > 0 && start >= stop)) { length = 0; } else if (step < 0) { length = Math.floor((stop - start + 1) / step + 1); } else { length = Math.floor((stop - start - 1) / step + 1); } // Compute the sliced result. let result: T[] = []; for (let i = 0; i < length; ++i) { result[i] = array[start + i * step]; } // Return the result. return result; } /** * The namespace for the `slice` function statics. */ export namespace slice { /** * The options for the `slice` function. */ export interface IOptions { /** * The starting index of the slice, inclusive. * * Negative values are taken as an offset from the end * of the array. * * The default is `0` if `step > 0` else `n - 1`. */ start?: number; /** * The stopping index of the slice, exclusive. * * Negative values are taken as an offset from the end * of the array. * * The default is `n` if `step > 0` else `-n - 1`. */ stop?: number; /** * The step value for the slice. * * This must not be `0`. * * The default is `1`. */ step?: number; } } /** * An array-like object which supports item assignment. */ export type MutableArrayLike = { readonly length: number; [index: number]: T; }; /** * Move an element in an array from one index to another. * * @param array - The mutable array-like object of interest. * * @param fromIndex - The index of the element to move. Negative * values are taken as an offset from the end of the array. * * @param toIndex - The target index of the element. Negative * values are taken as an offset from the end of the array. * * #### Complexity * Linear. * * #### Undefined Behavior * A `fromIndex` or `toIndex` which is non-integral. * * #### Example * ```typescript * import { ArrayExt } from from '@lumino/algorithm'; * * let data = [0, 1, 2, 3, 4]; * ArrayExt.move(data, 1, 2); // [0, 2, 1, 3, 4] * ArrayExt.move(data, 4, 2); // [0, 2, 4, 1, 3] * ``` */ export function move( array: MutableArrayLike, fromIndex: number, toIndex: number ): void { let n = array.length; if (n <= 1) { return; } if (fromIndex < 0) { fromIndex = Math.max(0, fromIndex + n); } else { fromIndex = Math.min(fromIndex, n - 1); } if (toIndex < 0) { toIndex = Math.max(0, toIndex + n); } else { toIndex = Math.min(toIndex, n - 1); } if (fromIndex === toIndex) { return; } let value = array[fromIndex]; let d = fromIndex < toIndex ? 1 : -1; for (let i = fromIndex; i !== toIndex; i += d) { array[i] = array[i + d]; } array[toIndex] = value; } /** * Reverse an array in-place. * * @param array - The mutable array-like object of interest. * * @param start - The index of the first element in the range to be * reversed, inclusive. The default value is `0`. Negative values * are taken as an offset from the end of the array. * * @param stop - The index of the last element in the range to be * reversed, inclusive. The default value is `-1`. Negative values * are taken as an offset from the end of the array. * * #### Complexity * Linear. * * #### Undefined Behavior * A `start` or `stop` index which is non-integral. * * #### Example * ```typescript * import { ArrayExt } from '@lumino/algorithm'; * * let data = [0, 1, 2, 3, 4]; * ArrayExt.reverse(data, 1, 3); // [0, 3, 2, 1, 4] * ArrayExt.reverse(data, 3); // [0, 3, 2, 4, 1] * ArrayExt.reverse(data); // [1, 4, 2, 3, 0] * ``` */ export function reverse( array: MutableArrayLike, start = 0, stop = -1 ): void { let n = array.length; if (n <= 1) { return; } if (start < 0) { start = Math.max(0, start + n); } else { start = Math.min(start, n - 1); } if (stop < 0) { stop = Math.max(0, stop + n); } else { stop = Math.min(stop, n - 1); } while (start < stop) { let a = array[start]; let b = array[stop]; array[start++] = b; array[stop--] = a; } } /** * Rotate the elements of an array in-place. * * @param array - The mutable array-like object of interest. * * @param delta - The amount of rotation to apply to the elements. A * positive value will rotate the elements to the left. A negative * value will rotate the elements to the right. * * @param start - The index of the first element in the range to be * rotated, inclusive. The default value is `0`. Negative values * are taken as an offset from the end of the array. * * @param stop - The index of the last element in the range to be * rotated, inclusive. The default value is `-1`. Negative values * are taken as an offset from the end of the array. * * #### Complexity * Linear. * * #### Undefined Behavior * A `delta`, `start`, or `stop` which is non-integral. * * #### Example * ```typescript * import { ArrayExt } from '@lumino/algorithm'; * * let data = [0, 1, 2, 3, 4]; * ArrayExt.rotate(data, 2); // [2, 3, 4, 0, 1] * ArrayExt.rotate(data, -2); // [0, 1, 2, 3, 4] * ArrayExt.rotate(data, 10); // [0, 1, 2, 3, 4] * ArrayExt.rotate(data, 9); // [4, 0, 1, 2, 3] * ArrayExt.rotate(data, 2, 1, 3); // [4, 2, 0, 1, 3] * ``` */ export function rotate( array: MutableArrayLike, delta: number, start = 0, stop = -1 ): void { let n = array.length; if (n <= 1) { return; } if (start < 0) { start = Math.max(0, start + n); } else { start = Math.min(start, n - 1); } if (stop < 0) { stop = Math.max(0, stop + n); } else { stop = Math.min(stop, n - 1); } if (start >= stop) { return; } let length = stop - start + 1; if (delta > 0) { delta = delta % length; } else if (delta < 0) { delta = ((delta % length) + length) % length; } if (delta === 0) { return; } let pivot = start + delta; reverse(array, start, pivot - 1); reverse(array, pivot, stop); reverse(array, start, stop); } /** * Fill an array with a static value. * * @param array - The mutable array-like object to fill. * * @param value - The static value to use to fill the array. * * @param start - The index of the first element in the range to be * filled, inclusive. The default value is `0`. Negative values * are taken as an offset from the end of the array. * * @param stop - The index of the last element in the range to be * filled, inclusive. The default value is `-1`. Negative values * are taken as an offset from the end of the array. * * #### Notes * If `stop < start` the fill will wrap at the end of the array. * * #### Complexity * Linear. * * #### Undefined Behavior * A `start` or `stop` which is non-integral. * * #### Example * ```typescript * import { ArrayExt } from '@lumino/algorithm'; * * let data = ['one', 'two', 'three', 'four']; * ArrayExt.fill(data, 'r'); // ['r', 'r', 'r', 'r'] * ArrayExt.fill(data, 'g', 1); // ['r', 'g', 'g', 'g'] * ArrayExt.fill(data, 'b', 2, 3); // ['r', 'g', 'b', 'b'] * ArrayExt.fill(data, 'z', 3, 1); // ['z', 'z', 'b', 'z'] * ``` */ export function fill( array: MutableArrayLike, value: T, start = 0, stop = -1 ): void { let n = array.length; if (n === 0) { return; } if (start < 0) { start = Math.max(0, start + n); } else { start = Math.min(start, n - 1); } if (stop < 0) { stop = Math.max(0, stop + n); } else { stop = Math.min(stop, n - 1); } let span: number; if (stop < start) { span = stop + 1 + (n - start); } else { span = stop - start + 1; } for (let i = 0; i < span; ++i) { array[(start + i) % n] = value; } } /** * Insert a value into an array at a specific index. * * @param array - The array of interest. * * @param index - The index at which to insert the value. Negative * values are taken as an offset from the end of the array. * * @param value - The value to set at the specified index. * * #### Complexity * Linear. * * #### Undefined Behavior * An `index` which is non-integral. * * #### Example * ```typescript * import { ArrayExt } from '@lumino/algorithm'; * * let data = [0, 1, 2]; * ArrayExt.insert(data, 0, -1); // [-1, 0, 1, 2] * ArrayExt.insert(data, 2, 12); // [-1, 0, 12, 1, 2] * ArrayExt.insert(data, -1, 7); // [-1, 0, 12, 1, 7, 2] * ArrayExt.insert(data, 6, 19); // [-1, 0, 12, 1, 7, 2, 19] * ``` */ export function insert(array: Array, index: number, value: T): void { let n = array.length; if (index < 0) { index = Math.max(0, index + n); } else { index = Math.min(index, n); } for (let i = n; i > index; --i) { array[i] = array[i - 1]; } array[index] = value; } /** * Remove and return a value at a specific index in an array. * * @param array - The array of interest. * * @param index - The index of the value to remove. Negative values * are taken as an offset from the end of the array. * * @returns The value at the specified index, or `undefined` if the * index is out of range. * * #### Complexity * Linear. * * #### Undefined Behavior * An `index` which is non-integral. * * #### Example * ```typescript * import { ArrayExt } from '@lumino/algorithm'; * * let data = [0, 12, 23, 39, 14, 12, 75]; * ArrayExt.removeAt(data, 2); // 23 * ArrayExt.removeAt(data, -2); // 12 * ArrayExt.removeAt(data, 10); // undefined; * ``` */ export function removeAt(array: Array, index: number): T | undefined { let n = array.length; if (index < 0) { index += n; } if (index < 0 || index >= n) { return undefined; } let value = array[index]; for (let i = index + 1; i < n; ++i) { array[i - 1] = array[i]; } array.length = n - 1; return value; } /** * Remove the first occurrence of a value from an array. * * @param array - The array of interest. * * @param value - The value to remove from the array. Values are * compared using strict `===` equality. * * @param start - The index of the first element in the range to be * searched, inclusive. The default value is `0`. Negative values * are taken as an offset from the end of the array. * * @param stop - The index of the last element in the range to be * searched, inclusive. The default value is `-1`. Negative values * are taken as an offset from the end of the array. * * @returns The index of the removed value, or `-1` if the value * is not contained in the array. * * #### Notes * If `stop < start` the search will wrap at the end of the array. * * #### Complexity * Linear. * * #### Example * ```typescript * import { ArrayExt } from '@lumino/algorithm'; * * let data = [0, 12, 23, 39, 14, 12, 75]; * ArrayExt.removeFirstOf(data, 12); // 1 * ArrayExt.removeFirstOf(data, 17); // -1 * ArrayExt.removeFirstOf(data, 39, 3); // -1 * ArrayExt.removeFirstOf(data, 39, 3, 2); // 2 * ``` */ export function removeFirstOf( array: Array, value: T, start = 0, stop = -1 ): number { let index = firstIndexOf(array, value, start, stop); if (index !== -1) { removeAt(array, index); } return index; } /** * Remove the last occurrence of a value from an array. * * @param array - The array of interest. * * @param value - The value to remove from the array. Values are * compared using strict `===` equality. * * @param start - The index of the first element in the range to be * searched, inclusive. The default value is `-1`. Negative values * are taken as an offset from the end of the array. * * @param stop - The index of the last element in the range to be * searched, inclusive. The default value is `0`. Negative values * are taken as an offset from the end of the array. * * @returns The index of the removed value, or `-1` if the value * is not contained in the array. * * #### Notes * If `start < stop` the search will wrap at the end of the array. * * #### Complexity * Linear. * * #### Example * ```typescript * import { ArrayExt } from '@lumino/algorithm'; * * let data = [0, 12, 23, 39, 14, 12, 75]; * ArrayExt.removeLastOf(data, 12); // 5 * ArrayExt.removeLastOf(data, 17); // -1 * ArrayExt.removeLastOf(data, 39, 2); // -1 * ArrayExt.removeLastOf(data, 39, 2, 3); // 3 * ``` */ export function removeLastOf( array: Array, value: T, start = -1, stop = 0 ): number { let index = lastIndexOf(array, value, start, stop); if (index !== -1) { removeAt(array, index); } return index; } /** * Remove all occurrences of a value from an array. * * @param array - The array of interest. * * @param value - The value to remove from the array. Values are * compared using strict `===` equality. * * @param start - The index of the first element in the range to be * searched, inclusive. The default value is `0`. Negative values * are taken as an offset from the end of the array. * * @param stop - The index of the last element in the range to be * searched, inclusive. The default value is `-1`. Negative values * are taken as an offset from the end of the array. * * @returns The number of elements removed from the array. * * #### Notes * If `stop < start` the search will conceptually wrap at the end of * the array, however the array will be traversed front-to-back. * * #### Complexity * Linear. * * #### Example * ```typescript * import { ArrayExt } from '@lumino/algorithm'; * * let data = [14, 12, 23, 39, 14, 12, 19, 14]; * ArrayExt.removeAllOf(data, 12); // 2 * ArrayExt.removeAllOf(data, 17); // 0 * ArrayExt.removeAllOf(data, 14, 1, 4); // 1 * ``` */ export function removeAllOf( array: Array, value: T, start = 0, stop = -1 ): number { let n = array.length; if (n === 0) { return 0; } if (start < 0) { start = Math.max(0, start + n); } else { start = Math.min(start, n - 1); } if (stop < 0) { stop = Math.max(0, stop + n); } else { stop = Math.min(stop, n - 1); } let count = 0; for (let i = 0; i < n; ++i) { if (start <= stop && i >= start && i <= stop && array[i] === value) { count++; } else if ( stop < start && (i <= stop || i >= start) && array[i] === value ) { count++; } else if (count > 0) { array[i - count] = array[i]; } } if (count > 0) { array.length = n - count; } return count; } /** * Remove the first occurrence of a value which matches a predicate. * * @param array - The array of interest. * * @param fn - The predicate function to apply to the values. * * @param start - The index of the first element in the range to be * searched, inclusive. The default value is `0`. Negative values * are taken as an offset from the end of the array. * * @param stop - The index of the last element in the range to be * searched, inclusive. The default value is `-1`. Negative values * are taken as an offset from the end of the array. * * @returns The removed `{ index, value }`, which will be `-1` and * `undefined` if the value is not contained in the array. * * #### Notes * If `stop < start` the search will wrap at the end of the array. * * #### Complexity * Linear. * * #### Example * ```typescript * import { ArrayExt } from '@lumino/algorithm'; * * function isEven(value: number): boolean { * return value % 2 === 0; * } * * let data = [0, 12, 23, 39, 14, 12, 75]; * ArrayExt.removeFirstWhere(data, isEven); // { index: 0, value: 0 } * ArrayExt.removeFirstWhere(data, isEven, 2); // { index: 3, value: 14 } * ArrayExt.removeFirstWhere(data, isEven, 4); // { index: -1, value: undefined } * ``` */ export function removeFirstWhere( array: Array, fn: (value: T, index: number) => boolean, start = 0, stop = -1 ): { index: number; value: T | undefined } { let value: T | undefined; let index = findFirstIndex(array, fn, start, stop); if (index !== -1) { value = removeAt(array, index); } return { index, value }; } /** * Remove the last occurrence of a value which matches a predicate. * * @param array - The array of interest. * * @param fn - The predicate function to apply to the values. * * @param start - The index of the first element in the range to be * searched, inclusive. The default value is `-1`. Negative values * are taken as an offset from the end of the array. * * @param stop - The index of the last element in the range to be * searched, inclusive. The default value is `0`. Negative values * are taken as an offset from the end of the array. * * @returns The removed `{ index, value }`, which will be `-1` and * `undefined` if the value is not contained in the array. * * #### Notes * If `start < stop` the search will wrap at the end of the array. * * #### Complexity * Linear. * * #### Example * ```typescript * import { ArrayExt } from '@lumino/algorithm'; * * function isEven(value: number): boolean { * return value % 2 === 0; * } * * let data = [0, 12, 23, 39, 14, 12, 75]; * ArrayExt.removeLastWhere(data, isEven); // { index: 5, value: 12 } * ArrayExt.removeLastWhere(data, isEven, 2); // { index: 1, value: 12 } * ArrayExt.removeLastWhere(data, isEven, 2, 1); // { index: -1, value: undefined } * ``` */ export function removeLastWhere( array: Array, fn: (value: T, index: number) => boolean, start = -1, stop = 0 ): { index: number; value: T | undefined } { let value: T | undefined; let index = findLastIndex(array, fn, start, stop); if (index !== -1) { value = removeAt(array, index); } return { index, value }; } /** * Remove all occurrences of values which match a predicate. * * @param array - The array of interest. * * @param fn - The predicate function to apply to the values. * * @param start - The index of the first element in the range to be * searched, inclusive. The default value is `0`. Negative values * are taken as an offset from the end of the array. * * @param stop - The index of the last element in the range to be * searched, inclusive. The default value is `-1`. Negative values * are taken as an offset from the end of the array. * * @returns The number of elements removed from the array. * * #### Notes * If `stop < start` the search will conceptually wrap at the end of * the array, however the array will be traversed front-to-back. * * #### Complexity * Linear. * * #### Example * ```typescript * import { ArrayExt } from '@lumino/algorithm'; * * function isEven(value: number): boolean { * return value % 2 === 0; * } * * function isNegative(value: number): boolean { * return value < 0; * } * * let data = [0, 12, -13, -9, 23, 39, 14, -15, 12, 75]; * ArrayExt.removeAllWhere(data, isEven); // 4 * ArrayExt.removeAllWhere(data, isNegative, 0, 3); // 2 * ``` */ export function removeAllWhere( array: Array, fn: (value: T, index: number) => boolean, start = 0, stop = -1 ): number { let n = array.length; if (n === 0) { return 0; } if (start < 0) { start = Math.max(0, start + n); } else { start = Math.min(start, n - 1); } if (stop < 0) { stop = Math.max(0, stop + n); } else { stop = Math.min(stop, n - 1); } let count = 0; for (let i = 0; i < n; ++i) { if (start <= stop && i >= start && i <= stop && fn(array[i], i)) { count++; } else if (stop < start && (i <= stop || i >= start) && fn(array[i], i)) { count++; } else if (count > 0) { array[i - count] = array[i]; } } if (count > 0) { array.length = n - count; } return count; } } lumino-2021.12.13/packages/algorithm/src/chain.ts000066400000000000000000000050711415564225700214110ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { IIterator, iter, IterableOrArrayLike } from './iter'; /** * Chain together several iterables. * * @param objects - The iterable or array-like objects of interest. * * @returns An iterator which yields the values of the iterables * in the order in which they are supplied. * * #### Example * ```typescript * import { chain, toArray } from '@lumino/algorithm'; * * let data1 = [1, 2, 3]; * let data2 = [4, 5, 6]; * * let stream = chain(data1, data2); * * toArray(stream); // [1, 2, 3, 4, 5, 6] * ``` */ export function chain(...objects: IterableOrArrayLike[]): IIterator { return new ChainIterator(iter(objects.map(iter))); } /** * An iterator which chains together several iterators. */ export class ChainIterator implements IIterator { /** * Construct a new chain iterator. * * @param source - The iterator of iterators of interest. */ constructor(source: IIterator>) { this._source = source; this._active = undefined; } /** * Get an iterator over the object's values. * * @returns An iterator which yields the object's values. */ iter(): IIterator { return this; } /** * Create an independent clone of the iterator. * * @returns A new independent clone of the iterator. */ clone(): IIterator { let result = new ChainIterator(this._source.clone()); result._active = this._active && this._active.clone(); result._cloned = true; this._cloned = true; return result; } /** * Get the next value from the iterator. * * @returns The next value from the iterator, or `undefined`. */ next(): T | undefined { if (this._active === undefined) { let active = this._source.next(); if (active === undefined) { return undefined; } this._active = this._cloned ? active.clone() : active; } let value = this._active.next(); if (value !== undefined) { return value; } this._active = undefined; return this.next(); } private _source: IIterator>; private _active: IIterator | undefined; private _cloned = false; } lumino-2021.12.13/packages/algorithm/src/empty.ts000066400000000000000000000026641415564225700214720ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { IIterator } from './iter'; /** * Create an empty iterator. * * @returns A new iterator which yields nothing. * * #### Example * ```typescript * import { empty, toArray } from '@lumino/algorithm'; * * let stream = empty(); * * toArray(stream); // [] * ``` */ export function empty(): IIterator { return new EmptyIterator(); } /** * An iterator which is always empty. */ export class EmptyIterator implements IIterator { /** * Get an iterator over the object's values. * * @returns An iterator which yields the object's values. */ iter(): IIterator { return this; } /** * Create an independent clone of the iterator. * * @returns A new independent clone of the iterator. */ clone(): IIterator { return new EmptyIterator(); } /** * Get the next value from the iterator. * * @returns The next value from the iterator, or `undefined`. */ next(): T | undefined { return undefined; } } lumino-2021.12.13/packages/algorithm/src/enumerate.ts000066400000000000000000000044461415564225700223210ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { IIterator, iter, IterableOrArrayLike } from './iter'; /** * Enumerate an iterable object. * * @param object - The iterable or array-like object of interest. * * @param start - The starting enum value. The default is `0`. * * @returns An iterator which yields the enumerated values. * * #### Example * ```typescript * import { enumerate, toArray } from '@lumino/algorithm'; * * let data = ['foo', 'bar', 'baz']; * * let stream = enumerate(data, 1); * * toArray(stream); // [[1, 'foo'], [2, 'bar'], [3, 'baz']] * ``` */ export function enumerate( object: IterableOrArrayLike, start = 0 ): IIterator<[number, T]> { return new EnumerateIterator(iter(object), start); } /** * An iterator which enumerates the source values. */ export class EnumerateIterator implements IIterator<[number, T]> { /** * Construct a new enumerate iterator. * * @param source - The iterator of values of interest. * * @param start - The starting enum value. */ constructor(source: IIterator, start: number) { this._source = source; this._index = start; } /** * Get an iterator over the object's values. * * @returns An iterator which yields the object's values. */ iter(): IIterator<[number, T]> { return this; } /** * Create an independent clone of the iterator. * * @returns A new independent clone of the iterator. */ clone(): IIterator<[number, T]> { return new EnumerateIterator(this._source.clone(), this._index); } /** * Get the next value from the iterator. * * @returns The next value from the iterator, or `undefined`. */ next(): [number, T] | undefined { let value = this._source.next(); if (value === undefined) { return undefined; } return [this._index++, value]; } private _source: IIterator; private _index: number; } lumino-2021.12.13/packages/algorithm/src/filter.ts000066400000000000000000000050011415564225700216050ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { IIterator, iter, IterableOrArrayLike } from './iter'; /** * Filter an iterable for values which pass a test. * * @param object - The iterable or array-like object of interest. * * @param fn - The predicate function to invoke for each value. * * @returns An iterator which yields the values which pass the test. * * #### Example * ```typescript * import { filter, toArray } from '@lumino/algorithm'; * * let data = [1, 2, 3, 4, 5, 6]; * * let stream = filter(data, value => value % 2 === 0); * * toArray(stream); // [2, 4, 6] * ``` */ export function filter( object: IterableOrArrayLike, fn: (value: T, index: number) => boolean ): IIterator { return new FilterIterator(iter(object), fn); } /** * An iterator which yields values which pass a test. */ export class FilterIterator implements IIterator { /** * Construct a new filter iterator. * * @param source - The iterator of values of interest. * * @param fn - The predicate function to invoke for each value. */ constructor(source: IIterator, fn: (value: T, index: number) => boolean) { this._source = source; this._fn = fn; } /** * Get an iterator over the object's values. * * @returns An iterator which yields the object's values. */ iter(): IIterator { return this; } /** * Create an independent clone of the iterator. * * @returns A new independent clone of the iterator. */ clone(): IIterator { let result = new FilterIterator(this._source.clone(), this._fn); result._index = this._index; return result; } /** * Get the next value from the iterator. * * @returns The next value from the iterator, or `undefined`. */ next(): T | undefined { let fn = this._fn; let it = this._source; let value: T | undefined; while ((value = it.next()) !== undefined) { if (fn(value, this._index++)) { return value; } } return undefined; } private _index = 0; private _source: IIterator; private _fn: (value: T, index: number) => boolean; } lumino-2021.12.13/packages/algorithm/src/find.ts000066400000000000000000000142771415564225700212570ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { iter, IterableOrArrayLike } from './iter'; /** * Find the first value in an iterable which matches a predicate. * * @param object - The iterable or array-like object to search. * * @param fn - The predicate function to apply to the values. * * @returns The first matching value, or `undefined` if no matching * value is found. * * #### Complexity * Linear. * * #### Example * ```typescript * import { find } from '@lumino/algorithm'; * * interface IAnimal { species: string, name: string }; * * function isCat(value: IAnimal): boolean { * return value.species === 'cat'; * } * * let data: IAnimal[] = [ * { species: 'dog', name: 'spot' }, * { species: 'cat', name: 'fluffy' }, * { species: 'alligator', name: 'pocho' } * ]; * * find(data, isCat).name; // 'fluffy' * ``` */ export function find( object: IterableOrArrayLike, fn: (value: T, index: number) => boolean ): T | undefined { let index = 0; let it = iter(object); let value: T | undefined; while ((value = it.next()) !== undefined) { if (fn(value, index++)) { return value; } } return undefined; } /** * Find the index of the first value which matches a predicate. * * @param object - The iterable or array-like object to search. * * @param fn - The predicate function to apply to the values. * * @returns The index of the first matching value, or `-1` if no * matching value is found. * * #### Complexity * Linear. * * #### Example * ```typescript * import { findIndex } from '@lumino/algorithm'; * * interface IAnimal { species: string, name: string }; * * function isCat(value: IAnimal): boolean { * return value.species === 'cat'; * } * * let data: IAnimal[] = [ * { species: 'dog', name: 'spot' }, * { species: 'cat', name: 'fluffy' }, * { species: 'alligator', name: 'pocho' } * ]; * * findIndex(data, isCat); // 1 * ``` */ export function findIndex( object: IterableOrArrayLike, fn: (value: T, index: number) => boolean ): number { let index = 0; let it = iter(object); let value: T | undefined; while ((value = it.next()) !== undefined) { if (fn(value, index++)) { return index - 1; } } return -1; } /** * Find the minimum value in an iterable. * * @param object - The iterable or array-like object to search. * * @param fn - The 3-way comparison function to apply to the values. * It should return `< 0` if the first value is less than the second. * `0` if the values are equivalent, or `> 0` if the first value is * greater than the second. * * @returns The minimum value in the iterable. If multiple values are * equivalent to the minimum, the left-most value is returned. If * the iterable is empty, this returns `undefined`. * * #### Complexity * Linear. * * #### Example * ```typescript * import { min } from '@lumino/algorithm'; * * function numberCmp(a: number, b: number): number { * return a - b; * } * * min([7, 4, 0, 3, 9, 4], numberCmp); // 0 * ``` */ export function min( object: IterableOrArrayLike, fn: (first: T, second: T) => number ): T | undefined { let it = iter(object); let value = it.next(); if (value === undefined) { return undefined; } let result = value; while ((value = it.next()) !== undefined) { if (fn(value, result) < 0) { result = value; } } return result; } /** * Find the maximum value in an iterable. * * @param object - The iterable or array-like object to search. * * @param fn - The 3-way comparison function to apply to the values. * It should return `< 0` if the first value is less than the second. * `0` if the values are equivalent, or `> 0` if the first value is * greater than the second. * * @returns The maximum value in the iterable. If multiple values are * equivalent to the maximum, the left-most value is returned. If * the iterable is empty, this returns `undefined`. * * #### Complexity * Linear. * * #### Example * ```typescript * import { max } from '@lumino/algorithm'; * * function numberCmp(a: number, b: number): number { * return a - b; * } * * max([7, 4, 0, 3, 9, 4], numberCmp); // 9 * ``` */ export function max( object: IterableOrArrayLike, fn: (first: T, second: T) => number ): T | undefined { let it = iter(object); let value = it.next(); if (value === undefined) { return undefined; } let result = value; while ((value = it.next()) !== undefined) { if (fn(value, result) > 0) { result = value; } } return result; } /** * Find the minimum and maximum values in an iterable. * * @param object - The iterable or array-like object to search. * * @param fn - The 3-way comparison function to apply to the values. * It should return `< 0` if the first value is less than the second. * `0` if the values are equivalent, or `> 0` if the first value is * greater than the second. * * @returns A 2-tuple of the `[min, max]` values in the iterable. If * multiple values are equivalent, the left-most values are returned. * If the iterable is empty, this returns `undefined`. * * #### Complexity * Linear. * * #### Example * ```typescript * import { minmax } from '@lumino/algorithm'; * * function numberCmp(a: number, b: number): number { * return a - b; * } * * minmax([7, 4, 0, 3, 9, 4], numberCmp); // [0, 9] * ``` */ export function minmax( object: IterableOrArrayLike, fn: (first: T, second: T) => number ): [T, T] | undefined { let it = iter(object); let value = it.next(); if (value === undefined) { return undefined; } let vmin = value; let vmax = value; while ((value = it.next()) !== undefined) { if (fn(value, vmin) < 0) { vmin = value; } else if (fn(value, vmax) > 0) { vmax = value; } } return [vmin, vmax]; } lumino-2021.12.13/packages/algorithm/src/index.ts000066400000000000000000000015561415564225700214420ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ export * from './array'; export * from './chain'; export * from './empty'; export * from './enumerate'; export * from './filter'; export * from './find'; export * from './iter'; export * from './map'; export * from './range'; export * from './reduce'; export * from './repeat'; export * from './retro'; export * from './sort'; export * from './stride'; export * from './string'; export * from './take'; export * from './zip'; lumino-2021.12.13/packages/algorithm/src/iter.ts000066400000000000000000000362641415564225700213020ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ /** * An object which can produce an iterator over its values. */ export interface IIterable { /** * Get an iterator over the object's values. * * @returns An iterator which yields the object's values. * * #### Notes * Depending on the iterable, the returned iterator may or may not be * a new object. A collection or other container-like object should * typically return a new iterator, while an iterator itself should * normally return `this`. */ iter(): IIterator; } /** * An object which traverses a collection of values. * * #### Notes * An `IIterator` is itself an `IIterable`. Most implementations of * `IIterator` should simply return `this` from the `iter()` method. */ export interface IIterator extends IIterable { /** * Create an independent clone of the iterator. * * @returns A new independent clone of the iterator. * * #### Notes * The cloned iterator can be consumed independently of the current * iterator. In essence, it is a copy of the iterator value stream * which starts at the current location. * * This can be useful for lookahead and stream duplication. */ clone(): IIterator; /** * Get the next value from the iterator. * * @returns The next value from the iterator, or `undefined`. * * #### Notes * The `undefined` value is used to signal the end of iteration and * should therefore not be used as a value in a collection. * * The use of the `undefined` sentinel is an explicit design choice * which favors performance over purity. The ES6 iterator design of * returning a `{ value, done }` pair is suboptimal, as it requires * an object allocation on each iteration; and an `isDone()` method * would increase implementation and runtime complexity. */ next(): T | undefined; } /** * A type alias for an iterable or builtin array-like object. */ export type IterableOrArrayLike = IIterable | ArrayLike; /** * Create an iterator for an iterable object. * * @param object - The iterable or array-like object of interest. * * @returns A new iterator for the given object. * * #### Notes * This function allows iteration algorithms to operate on user-defined * iterable types and builtin array-like objects in a uniform fashion. */ export function iter(object: IterableOrArrayLike): IIterator { let it: IIterator; if (typeof (object as any).iter === 'function') { it = (object as IIterable).iter(); } else { it = new ArrayIterator(object as ArrayLike); } return it; } /** * Create an iterator for the keys in an object. * * @param object - The object of interest. * * @returns A new iterator for the keys in the given object. * * #### Complexity * Linear. * * #### Example * ```typescript * import { each, keys } from '@lumino/algorithm'; * * let data = { one: 1, two: 2, three: 3 }; * * each(keys(data), key => { console.log(key); }); // 'one', 'two', 'three' * ``` */ export function iterKeys(object: { readonly [key: string]: T; }): IIterator { return new KeyIterator(object); } /** * Create an iterator for the values in an object. * * @param object - The object of interest. * * @returns A new iterator for the values in the given object. * * #### Complexity * Linear. * * #### Example * ```typescript * import { each, values } from '@lumino/algorithm'; * * let data = { one: 1, two: 2, three: 3 }; * * each(values(data), value => { console.log(value); }); // 1, 2, 3 * ``` */ export function iterValues(object: { readonly [key: string]: T; }): IIterator { return new ValueIterator(object); } /** * Create an iterator for the items in an object. * * @param object - The object of interest. * * @returns A new iterator for the items in the given object. * * #### Complexity * Linear. * * #### Example * ```typescript * import { each, items } from '@lumino/algorithm'; * * let data = { one: 1, two: 2, three: 3 }; * * each(items(data), value => { console.log(value); }); // ['one', 1], ['two', 2], ['three', 3] * ``` */ export function iterItems(object: { readonly [key: string]: T; }): IIterator<[string, T]> { return new ItemIterator(object); } /** * Create an iterator for an iterator-like function. * * @param fn - A function which behaves like an iterator `next` method. * * @returns A new iterator for the given function. * * #### Notes * The returned iterator **cannot** be cloned. * * #### Example * ```typescript * import { each, iterFn } from '@lumino/algorithm'; * * let it = iterFn((() => { * let i = 0; * return () => i > 3 ? undefined : i++; * })()); * * each(it, v => { console.log(v); }); // 0, 1, 2, 3 * ``` */ export function iterFn(fn: () => T | undefined): IIterator { return new FnIterator(fn); } /** * Invoke a function for each value in an iterable. * * @param object - The iterable or array-like object of interest. * * @param fn - The callback function to invoke for each value. * * #### Notes * Iteration can be terminated early by returning `false` from the * callback function. * * #### Complexity * Linear. * * #### Example * ```typescript * import { each } from '@lumino/algorithm'; * * let data = [5, 7, 0, -2, 9]; * * each(data, value => { console.log(value); }); * ``` */ export function each( object: IterableOrArrayLike, fn: (value: T, index: number) => boolean | void ): void { let index = 0; let it = iter(object); let value: T | undefined; while ((value = it.next()) !== undefined) { if (fn(value, index++) === false) { return; } } } /** * Test whether all values in an iterable satisfy a predicate. * * @param object - The iterable or array-like object of interest. * * @param fn - The predicate function to invoke for each value. * * @returns `true` if all values pass the test, `false` otherwise. * * #### Notes * Iteration terminates on the first `false` predicate result. * * #### Complexity * Linear. * * #### Example * ```typescript * import { every } from '@lumino/algorithm'; * * let data = [5, 7, 1]; * * every(data, value => value % 2 === 0); // false * every(data, value => value % 2 === 1); // true * ``` */ export function every( object: IterableOrArrayLike, fn: (value: T, index: number) => boolean ): boolean { let index = 0; let it = iter(object); let value: T | undefined; while ((value = it.next()) !== undefined) { if (!fn(value, index++)) { return false; } } return true; } /** * Test whether any value in an iterable satisfies a predicate. * * @param object - The iterable or array-like object of interest. * * @param fn - The predicate function to invoke for each value. * * @returns `true` if any value passes the test, `false` otherwise. * * #### Notes * Iteration terminates on the first `true` predicate result. * * #### Complexity * Linear. * * #### Example * ```typescript * import { some } from '@lumino/algorithm'; * * let data = [5, 7, 1]; * * some(data, value => value === 7); // true * some(data, value => value === 3); // false * ``` */ export function some( object: IterableOrArrayLike, fn: (value: T, index: number) => boolean ): boolean { let index = 0; let it = iter(object); let value: T | undefined; while ((value = it.next()) !== undefined) { if (fn(value, index++)) { return true; } } return false; } /** * Create an array from an iterable of values. * * @param object - The iterable or array-like object of interest. * * @returns A new array of values from the given object. * * #### Example * ```typescript * import { iter, toArray } from '@lumino/algorithm'; * * let data = [1, 2, 3, 4, 5, 6]; * * let stream = iter(data); * * toArray(stream); // [1, 2, 3, 4, 5, 6]; * ``` */ export function toArray(object: IterableOrArrayLike): T[] { let index = 0; let result: T[] = []; let it = iter(object); let value: T | undefined; while ((value = it.next()) !== undefined) { result[index++] = value; } return result; } /** * Create an object from an iterable of key/value pairs. * * @param object - The iterable or array-like object of interest. * * @returns A new object mapping keys to values. * * #### Example * ```typescript * import { toObject } from '@lumino/algorithm'; * * let data = [['one', 1], ['two', 2], ['three', 3]]; * * toObject(data); // { one: 1, two: 2, three: 3 } * ``` */ export function toObject( object: IterableOrArrayLike<[string, T]> ): { [key: string]: T } { let it = iter(object); let pair: [string, T] | undefined; let result: { [key: string]: T } = {}; while ((pair = it.next()) !== undefined) { result[pair[0]] = pair[1]; } return result; } /** * An iterator for an array-like object. * * #### Notes * This iterator can be used for any builtin JS array-like object. */ export class ArrayIterator implements IIterator { /** * Construct a new array iterator. * * @param source - The array-like object of interest. */ constructor(source: ArrayLike) { this._source = source; } /** * Get an iterator over the object's values. * * @returns An iterator which yields the object's values. */ iter(): IIterator { return this; } /** * Create an independent clone of the iterator. * * @returns A new independent clone of the iterator. */ clone(): IIterator { let result = new ArrayIterator(this._source); result._index = this._index; return result; } /** * Get the next value from the iterator. * * @returns The next value from the iterator, or `undefined`. */ next(): T | undefined { if (this._index >= this._source.length) { return undefined; } return this._source[this._index++]; } private _index = 0; private _source: ArrayLike; } /** * An iterator for the keys in an object. * * #### Notes * This iterator can be used for any JS object. */ export class KeyIterator implements IIterator { /** * Construct a new key iterator. * * @param source - The object of interest. * * @param keys - The keys to iterate, if known. */ constructor( source: { readonly [key: string]: any }, keys = Object.keys(source) ) { this._source = source; this._keys = keys; } /** * Get an iterator over the object's values. * * @returns An iterator which yields the object's values. */ iter(): IIterator { return this; } /** * Create an independent clone of the iterator. * * @returns A new independent clone of the iterator. */ clone(): IIterator { let result = new KeyIterator(this._source, this._keys); result._index = this._index; return result; } /** * Get the next value from the iterator. * * @returns The next value from the iterator, or `undefined`. */ next(): string | undefined { if (this._index >= this._keys.length) { return undefined; } let key = this._keys[this._index++]; if (key in this._source) { return key; } return this.next(); } private _index = 0; private _keys: string[]; private _source: { readonly [key: string]: any }; } /** * An iterator for the values in an object. * * #### Notes * This iterator can be used for any JS object. */ export class ValueIterator implements IIterator { /** * Construct a new value iterator. * * @param source - The object of interest. * * @param keys - The keys to iterate, if known. */ constructor( source: { readonly [key: string]: T }, keys = Object.keys(source) ) { this._source = source; this._keys = keys; } /** * Get an iterator over the object's values. * * @returns An iterator which yields the object's values. */ iter(): IIterator { return this; } /** * Create an independent clone of the iterator. * * @returns A new independent clone of the iterator. */ clone(): IIterator { let result = new ValueIterator(this._source, this._keys); result._index = this._index; return result; } /** * Get the next value from the iterator. * * @returns The next value from the iterator, or `undefined`. */ next(): T | undefined { if (this._index >= this._keys.length) { return undefined; } let key = this._keys[this._index++]; if (key in this._source) { return this._source[key]; } return this.next(); } private _index = 0; private _keys: string[]; private _source: { readonly [key: string]: T }; } /** * An iterator for the items in an object. * * #### Notes * This iterator can be used for any JS object. */ export class ItemIterator implements IIterator<[string, T]> { /** * Construct a new item iterator. * * @param source - The object of interest. * * @param keys - The keys to iterate, if known. */ constructor( source: { readonly [key: string]: T }, keys = Object.keys(source) ) { this._source = source; this._keys = keys; } /** * Get an iterator over the object's values. * * @returns An iterator which yields the object's values. */ iter(): IIterator<[string, T]> { return this; } /** * Create an independent clone of the iterator. * * @returns A new independent clone of the iterator. */ clone(): IIterator<[string, T]> { let result = new ItemIterator(this._source, this._keys); result._index = this._index; return result; } /** * Get the next value from the iterator. * * @returns The next value from the iterator, or `undefined`. */ next(): [string, T] | undefined { if (this._index >= this._keys.length) { return undefined; } let key = this._keys[this._index++]; if (key in this._source) { return [key, this._source[key]]; } return this.next(); } private _index = 0; private _keys: string[]; private _source: { readonly [key: string]: T }; } /** * An iterator for an iterator-like function. */ export class FnIterator implements IIterator { /** * Construct a new function iterator. * * @param fn - The iterator-like function of interest. */ constructor(fn: () => T | undefined) { this._fn = fn; } /** * Get an iterator over the object's values. * * @returns An iterator which yields the object's values. */ iter(): IIterator { return this; } /** * Create an independent clone of the iterator. * * @returns A new independent clone of the iterator. */ clone(): IIterator { throw new Error('An `FnIterator` cannot be cloned.'); } /** * Get the next value from the iterator. * * @returns The next value from the iterator, or `undefined`. */ next(): T | undefined { return this._fn.call(undefined); } private _fn: () => T | undefined; } lumino-2021.12.13/packages/algorithm/src/map.ts000066400000000000000000000046371415564225700211130ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { IIterator, iter, IterableOrArrayLike } from './iter'; /** * Transform the values of an iterable with a mapping function. * * @param object - The iterable or array-like object of interest. * * @param fn - The mapping function to invoke for each value. * * @returns An iterator which yields the transformed values. * * #### Example * ```typescript * import { map, toArray } from '@lumino/algorithm'; * * let data = [1, 2, 3]; * * let stream = map(data, value => value * 2); * * toArray(stream); // [2, 4, 6] * ``` */ export function map( object: IterableOrArrayLike, fn: (value: T, index: number) => U ): IIterator { return new MapIterator(iter(object), fn); } /** * An iterator which transforms values using a mapping function. */ export class MapIterator implements IIterator { /** * Construct a new map iterator. * * @param source - The iterator of values of interest. * * @param fn - The mapping function to invoke for each value. */ constructor(source: IIterator, fn: (value: T, index: number) => U) { this._source = source; this._fn = fn; } /** * Get an iterator over the object's values. * * @returns An iterator which yields the object's values. */ iter(): IIterator { return this; } /** * Create an independent clone of the iterator. * * @returns A new independent clone of the iterator. */ clone(): IIterator { let result = new MapIterator(this._source.clone(), this._fn); result._index = this._index; return result; } /** * Get the next value from the iterator. * * @returns The next value from the iterator, or `undefined`. */ next(): U | undefined { let value = this._source.next(); if (value === undefined) { return undefined; } return this._fn.call(undefined, value, this._index++); } private _index = 0; private _source: IIterator; private _fn: (value: T, index: number) => U; } lumino-2021.12.13/packages/algorithm/src/range.ts000066400000000000000000000065761415564225700214360ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { IIterator } from './iter'; /** * Create an iterator of evenly spaced values. * * @param start - The starting value for the range, inclusive. * * @param stop - The stopping value for the range, exclusive. * * @param step - The distance between each value. * * @returns An iterator which produces evenly spaced values. * * #### Notes * In the single argument form of `range(stop)`, `start` defaults to * `0` and `step` defaults to `1`. * * In the two argument form of `range(start, stop)`, `step` defaults * to `1`. */ export function range( start: number, stop?: number, step?: number ): IIterator { if (stop === undefined) { return new RangeIterator(0, start, 1); } if (step === undefined) { return new RangeIterator(start, stop, 1); } return new RangeIterator(start, stop, step); } /** * An iterator which produces a range of evenly spaced values. */ export class RangeIterator implements IIterator { /** * Construct a new range iterator. * * @param start - The starting value for the range, inclusive. * * @param stop - The stopping value for the range, exclusive. * * @param step - The distance between each value. */ constructor(start: number, stop: number, step: number) { this._start = start; this._stop = stop; this._step = step; this._length = Private.rangeLength(start, stop, step); } /** * Get an iterator over the object's values. * * @returns An iterator which yields the object's values. */ iter(): IIterator { return this; } /** * Create an independent clone of the iterator. * * @returns A new independent clone of the iterator. */ clone(): IIterator { let result = new RangeIterator(this._start, this._stop, this._step); result._index = this._index; return result; } /** * Get the next value from the iterator. * * @returns The next value from the iterator, or `undefined`. */ next(): number | undefined { if (this._index >= this._length) { return undefined; } return this._start + this._step * this._index++; } private _index = 0; private _length: number; private _start: number; private _stop: number; private _step: number; } /** * The namespace for the module implementation details. */ namespace Private { /** * Compute the effective length of a range. * * @param start - The starting value for the range, inclusive. * * @param stop - The stopping value for the range, exclusive. * * @param step - The distance between each value. * * @returns The number of steps need to traverse the range. */ export function rangeLength( start: number, stop: number, step: number ): number { if (step === 0) { return Infinity; } if (start > stop && step > 0) { return 0; } if (start < stop && step < 0) { return 0; } return Math.ceil((stop - start) / step); } } lumino-2021.12.13/packages/algorithm/src/reduce.ts000066400000000000000000000064321415564225700216000ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { iter, IterableOrArrayLike } from './iter'; /** * Summarize all values in an iterable using a reducer function. * * @param object - The iterable or array-like object of interest. * * @param fn - The reducer function to invoke for each value. * * @param initial - The initial value to start accumulation. * * @returns The final accumulated value. * * #### Notes * The `reduce` function follows the conventions of `Array#reduce`. * * If the iterator is empty, an initial value is required. That value * will be used as the return value. If no initial value is provided, * an error will be thrown. * * If the iterator contains a single item and no initial value is * provided, the single item is used as the return value. * * Otherwise, the reducer is invoked for each element in the iterable. * If an initial value is not provided, the first element will be used * as the initial accumulated value. * * #### Complexity * Linear. * * #### Example * ```typescript * import { reduce } from '@lumino/algorithm'; * * let data = [1, 2, 3, 4, 5]; * * let sum = reduce(data, (a, value) => a + value); // 15 * ``` */ export function reduce( object: IterableOrArrayLike, fn: (accumulator: T, value: T, index: number) => T ): T; export function reduce( object: IterableOrArrayLike, fn: (accumulator: U, value: T, index: number) => U, initial: U ): U; export function reduce( object: IterableOrArrayLike, fn: (accumulator: any, value: T, index: number) => any, initial?: any ): any { // Setup the iterator and fetch the first value. let index = 0; let it = iter(object); let first = it.next(); // An empty iterator and no initial value is an error. if (first === undefined && initial === undefined) { throw new TypeError('Reduce of empty iterable with no initial value.'); } // If the iterator is empty, return the initial value. if (first === undefined) { return initial; } // If the iterator has a single item and no initial value, the // reducer is not invoked and the first item is the return value. let second = it.next(); if (second === undefined && initial === undefined) { return first; } // If iterator has a single item and an initial value is provided, // the reducer is invoked and that result is the return value. if (second === undefined) { return fn(initial, first, index++); } // Setup the initial accumlated value. let accumulator: any; if (initial === undefined) { accumulator = fn(first, second, index++); } else { accumulator = fn(fn(initial, first, index++), second, index++); } // Iterate the rest of the values, updating the accumulator. let next: T | undefined; while ((next = it.next()) !== undefined) { accumulator = fn(accumulator, next, index++); } // Return the final accumulated value. return accumulator; } lumino-2021.12.13/packages/algorithm/src/repeat.ts000066400000000000000000000047441415564225700216150ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { IIterator } from './iter'; /** * Create an iterator which repeats a value a number of times. * * @param value - The value to repeat. * * @param count - The number of times to repeat the value. * * @returns A new iterator which repeats the specified value. * * #### Example * ```typescript * import { repeat, toArray } from '@lumino/algorithm'; * * let stream = repeat(7, 3); * * toArray(stream); // [7, 7, 7] * ``` */ export function repeat(value: T, count: number): IIterator { return new RepeatIterator(value, count); } /** * Create an iterator which yields a value a single time. * * @param value - The value to wrap in an iterator. * * @returns A new iterator which yields the value a single time. * * #### Example * ```typescript * import { once, toArray } from '@lumino/algorithm'; * * let stream = once(7); * * toArray(stream); // [7] * ``` */ export function once(value: T): IIterator { return new RepeatIterator(value, 1); } /** * An iterator which repeats a value a specified number of times. */ export class RepeatIterator implements IIterator { /** * Construct a new repeat iterator. * * @param value - The value to repeat. * * @param count - The number of times to repeat the value. */ constructor(value: T, count: number) { this._value = value; this._count = count; } /** * Get an iterator over the object's values. * * @returns An iterator which yields the object's values. */ iter(): IIterator { return this; } /** * Create an independent clone of the iterator. * * @returns A new independent clone of the iterator. */ clone(): IIterator { return new RepeatIterator(this._value, this._count); } /** * Get the next value from the iterator. * * @returns The next value from the iterator, or `undefined`. */ next(): T | undefined { if (this._count <= 0) { return undefined; } this._count--; return this._value; } private _value: T; private _count: number; } lumino-2021.12.13/packages/algorithm/src/retro.ts000066400000000000000000000054071415564225700214650ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { IIterator } from './iter'; /** * An object which can produce a reverse iterator over its values. */ export interface IRetroable { /** * Get a reverse iterator over the object's values. * * @returns An iterator which yields the object's values in reverse. */ retro(): IIterator; } /** * A type alias for a retroable or builtin array-like object. */ export type RetroableOrArrayLike = IRetroable | ArrayLike; /** * Create an iterator for a retroable object. * * @param object - The retroable or array-like object of interest. * * @returns An iterator which traverses the object's values in reverse. * * #### Example * ```typescript * import { retro, toArray } from '@lumino/algorithm'; * * let data = [1, 2, 3, 4, 5, 6]; * * let stream = retro(data); * * toArray(stream); // [6, 5, 4, 3, 2, 1] * ``` */ export function retro(object: RetroableOrArrayLike): IIterator { let it: IIterator; if (typeof (object as any).retro === 'function') { it = (object as IRetroable).retro(); } else { it = new RetroArrayIterator(object as ArrayLike); } return it; } /** * An iterator which traverses an array-like object in reverse. * * #### Notes * This iterator can be used for any builtin JS array-like object. */ export class RetroArrayIterator implements IIterator { /** * Construct a new retro iterator. * * @param source - The array-like object of interest. */ constructor(source: ArrayLike) { this._source = source; this._index = source.length - 1; } /** * Get an iterator over the object's values. * * @returns An iterator which yields the object's values. */ iter(): IIterator { return this; } /** * Create an independent clone of the iterator. * * @returns A new independent clone of the iterator. */ clone(): IIterator { let result = new RetroArrayIterator(this._source); result._index = this._index; return result; } /** * Get the next value from the iterator. * * @returns The next value from the iterator, or `undefined`. */ next(): T | undefined { if (this._index < 0 || this._index >= this._source.length) { return undefined; } return this._source[this._index--]; } private _index: number; private _source: ArrayLike; } lumino-2021.12.13/packages/algorithm/src/sort.ts000066400000000000000000000040021415564225700213070ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { each, IterableOrArrayLike } from './iter'; /** * Topologically sort an iterable of edges. * * @param edges - The iterable or array-like object of edges to sort. * An edge is represented as a 2-tuple of `[fromNode, toNode]`. * * @returns The topologically sorted array of nodes. * * #### Notes * If a cycle is present in the graph, the cycle will be ignored and * the return value will be only approximately sorted. * * #### Example * ```typescript * import { topologicSort } from '@lumino/algorithm'; * * let data = [ * ['d', 'e'], * ['c', 'd'], * ['a', 'b'], * ['b', 'c'] * ]; * * topologicSort(data); // ['a', 'b', 'c', 'd', 'e'] * ``` */ export function topologicSort(edges: IterableOrArrayLike<[T, T]>): T[] { // Setup the shared sorting state. let sorted: T[] = []; let visited = new Set(); let graph = new Map(); // Add the edges to the graph. each(edges, addEdge); // Visit each node in the graph. graph.forEach((v, k) => { visit(k); }); // Return the sorted results. return sorted; // Add an edge to the graph. function addEdge(edge: [T, T]): void { let [fromNode, toNode] = edge; let children = graph.get(toNode); if (children) { children.push(fromNode); } else { graph.set(toNode, [fromNode]); } } // Recursively visit the node. function visit(node: T): void { if (visited.has(node)) { return; } visited.add(node); let children = graph.get(node); if (children) { children.forEach(visit); } sorted.push(node); } } lumino-2021.12.13/packages/algorithm/src/stride.ts000066400000000000000000000045721415564225700216260ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { IIterator, iter, IterableOrArrayLike } from './iter'; /** * Iterate over an iterable using a stepped increment. * * @param object - The iterable or array-like object of interest. * * @param step - The distance to step on each iteration. A value * of less than `1` will behave the same as a value of `1`. * * @returns An iterator which traverses the iterable step-wise. * * #### Example * ```typescript * import { stride, toArray } from '@lumino/algorithm'; * * let data = [1, 2, 3, 4, 5, 6]; * * let stream = stride(data, 2); * * toArray(stream); // [1, 3, 5]; * ``` */ export function stride( object: IterableOrArrayLike, step: number ): IIterator { return new StrideIterator(iter(object), step); } /** * An iterator which traverses a source iterator step-wise. */ export class StrideIterator implements IIterator { /** * Construct a new stride iterator. * * @param source - The iterator of values of interest. * * @param step - The distance to step on each iteration. A value * of less than `1` will behave the same as a value of `1`. */ constructor(source: IIterator, step: number) { this._source = source; this._step = step; } /** * Get an iterator over the object's values. * * @returns An iterator which yields the object's values. */ iter(): IIterator { return this; } /** * Create an independent clone of the iterator. * * @returns A new independent clone of the iterator. */ clone(): IIterator { return new StrideIterator(this._source.clone(), this._step); } /** * Get the next value from the iterator. * * @returns The next value from the iterator, or `undefined`. */ next(): T | undefined { let value = this._source.next(); for (let n = this._step - 1; n > 0; --n) { this._source.next(); } return value; } private _source: IIterator; private _step: number; } lumino-2021.12.13/packages/algorithm/src/string.ts000066400000000000000000000142701415564225700216360ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ /** * The namespace for string-specific algorithms. */ export namespace StringExt { /** * Find the indices of characters in a source text. * * @param source - The source text which should be searched. * * @param query - The characters to locate in the source text. * * @param start - The index to start the search. * * @returns The matched indices, or `null` if there is no match. * * #### Complexity * Linear on `sourceText`. * * #### Notes * In order for there to be a match, all of the characters in `query` * **must** appear in `source` in the order given by `query`. * * Characters are matched using strict `===` equality. */ export function findIndices( source: string, query: string, start = 0 ): number[] | null { let indices = new Array(query.length); for (let i = 0, j = start, n = query.length; i < n; ++i, ++j) { j = source.indexOf(query[i], j); if (j === -1) { return null; } indices[i] = j; } return indices; } /** * The result of a string match function. */ export interface IMatchResult { /** * A score which indicates the strength of the match. * * The documentation of a given match function should specify * whether a lower or higher score is a stronger match. */ score: number; /** * The indices of the matched characters in the source text. * * The indices will appear in increasing order. */ indices: number[]; } /** * A string matcher which uses a sum-of-squares algorithm. * * @param source - The source text which should be searched. * * @param query - The characters to locate in the source text. * * @param start - The index to start the search. * * @returns The match result, or `null` if there is no match. * A lower `score` represents a stronger match. * * #### Complexity * Linear on `sourceText`. * * #### Notes * This scoring algorithm uses a sum-of-squares approach to determine * the score. In order for there to be a match, all of the characters * in `query` **must** appear in `source` in order. The index of each * matching character is squared and added to the score. This means * that early and consecutive character matches are preferred, while * late matches are heavily penalized. */ export function matchSumOfSquares( source: string, query: string, start = 0 ): IMatchResult | null { let indices = findIndices(source, query, start); if (!indices) { return null; } let score = 0; for (let i = 0, n = indices.length; i < n; ++i) { let j = indices[i] - start; score += j * j; } return { score, indices }; } /** * A string matcher which uses a sum-of-deltas algorithm. * * @param source - The source text which should be searched. * * @param query - The characters to locate in the source text. * * @param start - The index to start the search. * * @returns The match result, or `null` if there is no match. * A lower `score` represents a stronger match. * * #### Complexity * Linear on `sourceText`. * * #### Notes * This scoring algorithm uses a sum-of-deltas approach to determine * the score. In order for there to be a match, all of the characters * in `query` **must** appear in `source` in order. The delta between * the indices are summed to create the score. This means that groups * of matched characters are preferred, while fragmented matches are * penalized. */ export function matchSumOfDeltas( source: string, query: string, start = 0 ): IMatchResult | null { let indices = findIndices(source, query, start); if (!indices) { return null; } let score = 0; let last = start - 1; for (let i = 0, n = indices.length; i < n; ++i) { let j = indices[i]; score += j - last - 1; last = j; } return { score, indices }; } /** * Highlight the matched characters of a source text. * * @param source - The text which should be highlighted. * * @param indices - The indices of the matched characters. They must * appear in increasing order and must be in bounds of the source. * * @param fn - The function to apply to the matched chunks. * * @returns An array of unmatched and highlighted chunks. */ export function highlight( source: string, indices: ReadonlyArray, fn: (chunk: string) => T ): Array { // Set up the result array. let result: Array = []; // Set up the counter variables. let k = 0; let last = 0; let n = indices.length; // Iterator over each index. while (k < n) { // Set up the chunk indices. let i = indices[k]; let j = indices[k]; // Advance the right chunk index until it's non-contiguous. while (++k < n && indices[k] === j + 1) { j++; } // Extract the unmatched text. if (last < i) { result.push(source.slice(last, i)); } // Extract and highlight the matched text. if (i < j + 1) { result.push(fn(source.slice(i, j + 1))); } // Update the last visited index. last = j + 1; } // Extract any remaining unmatched text. if (last < source.length) { result.push(source.slice(last)); } // Return the highlighted result. return result; } /** * A 3-way string comparison function. * * @param a - The first string of interest. * * @param b - The second string of interest. * * @returns `-1` if `a < b`, else `1` if `a > b`, else `0`. */ export function cmp(a: string, b: string): number { return a < b ? -1 : a > b ? 1 : 0; } } lumino-2021.12.13/packages/algorithm/src/take.ts000066400000000000000000000043711415564225700212550ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { IIterator, iter, IterableOrArrayLike } from './iter'; /** * Take a fixed number of items from an iterable. * * @param object - The iterable or array-like object of interest. * * @param count - The number of items to take from the iterable. * * @returns An iterator which yields the specified number of items * from the source iterable. * * #### Notes * The returned iterator will exhaust early if the source iterable * contains an insufficient number of items. */ export function take( object: IterableOrArrayLike, count: number ): IIterator { return new TakeIterator(iter(object), count); } /** * An iterator which takes a fixed number of items from a source. */ export class TakeIterator implements IIterator { /** * Construct a new take iterator. * * @param source - The iterator of interest. * * @param count - The number of items to take from the source. */ constructor(source: IIterator, count: number) { this._source = source; this._count = count; } /** * Get an iterator over the object's values. * * @returns An iterator which yields the object's values. */ iter(): IIterator { return this; } /** * Create an independent clone of the iterator. * * @returns A new independent clone of the iterator. */ clone(): IIterator { return new TakeIterator(this._source.clone(), this._count); } /** * Get the next value from the iterator. * * @returns The next value from the iterator, or `undefined`. */ next(): T | undefined { if (this._count <= 0) { return undefined; } let value = this._source.next(); if (value === undefined) { return undefined; } this._count--; return value; } private _count: number; private _source: IIterator; } lumino-2021.12.13/packages/algorithm/src/zip.ts000066400000000000000000000044651415564225700211370ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { IIterator, iter, IterableOrArrayLike } from './iter'; /** * Iterate several iterables in lockstep. * * @param objects - The iterable or array-like objects of interest. * * @returns An iterator which yields successive tuples of values where * each value is taken in turn from the provided iterables. It will * be as long as the shortest provided iterable. * * #### Example * ```typescript * import { zip, toArray } from '@lumino/algorithm'; * * let data1 = [1, 2, 3]; * let data2 = [4, 5, 6]; * * let stream = zip(data1, data2); * * toArray(stream); // [[1, 4], [2, 5], [3, 6]] * ``` */ export function zip(...objects: IterableOrArrayLike[]): IIterator { return new ZipIterator(objects.map(iter)); } /** * An iterator which iterates several sources in lockstep. */ export class ZipIterator implements IIterator { /** * Construct a new zip iterator. * * @param source - The iterators of interest. */ constructor(source: IIterator[]) { this._source = source; } /** * Get an iterator over the object's values. * * @returns An iterator which yields the object's values. */ iter(): IIterator { return this; } /** * Create an independent clone of the iterator. * * @returns A new independent clone of the iterator. */ clone(): IIterator { return new ZipIterator(this._source.map(it => it.clone())); } /** * Get the next value from the iterator. * * @returns The next value from the iterator, or `undefined`. */ next(): T[] | undefined { let result = new Array(this._source.length); for (let i = 0, n = this._source.length; i < n; ++i) { let value = this._source[i].next(); if (value === undefined) { return undefined; } result[i] = value; } return result; } private _source: IIterator[]; } lumino-2021.12.13/packages/algorithm/tdoptions.json000066400000000000000000000005201415564225700221000ustar00rootroot00000000000000{ "excludeNotExported": true, "mode": "file", "target": "es5", "module": "es5", "lib": [ "lib.es2015.d.ts", "lib.es2015.collection.d.ts", "lib.es2015.promise.d.ts", "lib.dom.d.ts" ], "out": "../../docs/source/api/algorithm", "baseUrl": ".", "paths": { "@lumino/*": ["node_modules/@lumino/*"] } } lumino-2021.12.13/packages/algorithm/tests/000077500000000000000000000000001415564225700203275ustar00rootroot00000000000000lumino-2021.12.13/packages/algorithm/tests/karma.conf.js000066400000000000000000000005051415564225700227040ustar00rootroot00000000000000module.exports = function (config) { config.set({ basePath: '.', frameworks: ['mocha'], reporters: ['mocha'], files: ['build/bundle.test.js'], port: 9876, colors: true, singleRun: true, browserNoActivityTimeout: 30000, failOnEmptyTestSuite: false, logLevel: config.LOG_INFO }); }; lumino-2021.12.13/packages/algorithm/tests/src/000077500000000000000000000000001415564225700211165ustar00rootroot00000000000000lumino-2021.12.13/packages/algorithm/tests/src/array.spec.ts000066400000000000000000001046241415564225700235440ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { expect } from 'chai'; import { ArrayExt } from '@lumino/algorithm'; describe('@lumino/algorithm', () => { describe('ArrayExt', () => { describe('firstIndexOf()', () => { it('should find the index of the first matching value', () => { let data = ['one', 'two', 'three', 'four', 'one']; let i = ArrayExt.firstIndexOf(data, 'one'); expect(i).to.equal(0); }); it('should return `-1` if there is no matching value', () => { let data = ['one', 'two', 'three', 'four', 'one']; let i = ArrayExt.firstIndexOf(data, 'red'); expect(i).to.equal(-1); }); it('should return `-1` if the array is empty', () => { let data: string[] = []; let i = ArrayExt.firstIndexOf(data, 'one'); expect(i).to.equal(-1); }); it('should support searching from a start index', () => { let data = ['one', 'two', 'three', 'four', 'one']; let i = ArrayExt.firstIndexOf(data, 'one', 2); expect(i).to.equal(4); }); it('should support a negative start index', () => { let data = ['one', 'two', 'three', 'four', 'one']; let i = ArrayExt.firstIndexOf(data, 'one', -2); expect(i).to.equal(4); }); it('should support searching within a range', () => { let data = ['one', 'two', 'one', 'four', 'one']; let i = ArrayExt.firstIndexOf(data, 'one', 1, 3); expect(i).to.equal(2); }); it('should support a negative stop index', () => { let data = ['one', 'two', 'one', 'four', 'one']; let i = ArrayExt.firstIndexOf(data, 'one', 1, -4); expect(i).to.equal(-1); }); it('should wrap around if stop < start', () => { let data = ['one', 'two', 'one', 'four', 'one']; let i = ArrayExt.firstIndexOf(data, 'two', 3, 2); expect(i).to.equal(1); }); }); describe('lastIndexOf()', () => { it('should find the index of the last matching value', () => { let data = ['one', 'two', 'three', 'four', 'one']; let i = ArrayExt.lastIndexOf(data, 'one'); expect(i).to.equal(4); }); it('should return `-1` if there is no matching value', () => { let data = ['one', 'two', 'three', 'four', 'one']; let i = ArrayExt.lastIndexOf(data, 'red'); expect(i).to.equal(-1); }); it('should return `-1` if the array is empty', () => { let data: string[] = []; let i = ArrayExt.lastIndexOf(data, 'one'); expect(i).to.equal(-1); }); it('should support searching from a start index', () => { let data = ['one', 'two', 'three', 'four', 'one']; let i = ArrayExt.lastIndexOf(data, 'one', 2); expect(i).to.equal(0); }); it('should support a negative start index', () => { let data = ['one', 'two', 'one', 'four', 'one']; let i = ArrayExt.lastIndexOf(data, 'one', -2); expect(i).to.equal(2); }); it('should support searching within a range', () => { let data = ['one', 'two', 'one', 'four', 'one']; let i = ArrayExt.lastIndexOf(data, 'one', 3, 1); expect(i).to.equal(2); }); it('should support a negative stop index', () => { let data = ['one', 'two', 'one', 'four', 'one']; let i = ArrayExt.lastIndexOf(data, 'one', 1, -4); expect(i).to.equal(-1); }); it('should wrap around if start < stop', () => { let data = ['one', 'two', 'one', 'four', 'one']; let i = ArrayExt.lastIndexOf(data, 'four', 2, 3); expect(i).to.equal(3); }); }); describe('findFirstIndex()', () => { it('should find the index of the first matching value', () => { let data = [1, 2, 3, 4, 5]; let i = ArrayExt.findFirstIndex(data, v => v % 2 === 0); expect(i).to.equal(1); }); it('should return `-1` if there is no matching value', () => { let data = [1, 2, 3, 4, 5]; let i = ArrayExt.findFirstIndex(data, v => v % 7 === 0); expect(i).to.equal(-1); }); it('should return `-1` if the array is empty', () => { let data: number[] = []; let i = ArrayExt.findFirstIndex(data, v => v % 2 === 0); expect(i).to.equal(-1); }); it('should support searching from a start index', () => { let data = [1, 2, 3, 4, 5]; let i = ArrayExt.findFirstIndex(data, v => v % 2 === 0, 2); expect(i).to.equal(3); }); it('should support a negative start index', () => { let data = [1, 2, 3, 4, 5]; let i = ArrayExt.findFirstIndex(data, v => v % 2 === 0, -3); expect(i).to.equal(3); }); it('should support searching within a range', () => { let data = [1, 2, 3, 4, 5]; let i = ArrayExt.findFirstIndex(data, v => v % 2 === 0, 2, 4); expect(i).to.equal(3); }); it('should support a negative stop index', () => { let data = [1, 2, 3, 4, 5]; let i = ArrayExt.findFirstIndex(data, v => v % 2 === 0, 2, -2); expect(i).to.equal(3); }); it('should wrap around if stop < start', () => { let data = [1, 2, 3, 4, 5]; let i = ArrayExt.findFirstIndex(data, v => v % 2 === 0, 4, 2); expect(i).to.equal(1); }); }); describe('findLastIndex()', () => { it('should find the index of the last matching value', () => { let data = [1, 2, 3, 4, 5]; let i = ArrayExt.findLastIndex(data, v => v % 2 === 0); expect(i).to.equal(3); }); it('should return `-1` if there is no matching value', () => { let data = [1, 2, 3, 4, 5]; let i = ArrayExt.findLastIndex(data, v => v % 7 === 0); expect(i).to.equal(-1); }); it('should return `-1` if the array is empty', () => { let data: number[] = []; let i = ArrayExt.findLastIndex(data, v => v % 2 === 0); expect(i).to.equal(-1); }); it('should support searching from a start index', () => { let data = [1, 2, 3, 4, 5]; let i = ArrayExt.findLastIndex(data, v => v % 2 === 0, 2); expect(i).to.equal(1); }); it('should support a negative start index', () => { let data = [1, 2, 3, 4, 5]; let i = ArrayExt.findLastIndex(data, v => v % 2 === 0, -3); expect(i).to.equal(1); }); it('should support searching within a range', () => { let data = [1, 2, 3, 4, 5]; let i = ArrayExt.findLastIndex(data, v => v % 2 === 0, 4, 2); expect(i).to.equal(3); }); it('should support a negative stop index', () => { let data = [1, 2, 3, 4, 5]; let i = ArrayExt.findLastIndex(data, v => v % 2 === 0, -3, 0); expect(i).to.equal(1); }); it('should wrap around if start < stop', () => { let data = [1, 2, 3, 4, 5]; let i = ArrayExt.findLastIndex(data, v => v % 2 === 0, 0, 2); expect(i).to.equal(3); }); }); describe('findFirstValue()', () => { it('should find the index of the first matching value', () => { let data = ['apple', 'bottle', 'cat', 'dog', 'egg', 'blue']; let i = ArrayExt.findFirstValue(data, v => v[0] === 'b'); expect(i).to.equal('bottle'); }); it('should return `undefined` if there is no matching value', () => { let data = ['apple', 'bottle', 'cat', 'dog', 'egg', 'fish']; let i = ArrayExt.findFirstValue(data, v => v[0] === 'z'); expect(i).to.equal(undefined); }); it('should return `undefined` if the array is empty', () => { let data: string[] = []; let i = ArrayExt.findFirstValue(data, v => v[0] === 'b'); expect(i).to.equal(undefined); }); it('should support searching from a start index', () => { let data = ['apple', 'eagle', 'cat', 'dog', 'egg', 'fish']; let i = ArrayExt.findFirstValue(data, v => v[0] === 'e', 2); expect(i).to.equal('egg'); }); it('should support a negative start index', () => { let data = ['apple', 'eagle', 'cat', 'dog', 'egg', 'fish']; let i = ArrayExt.findFirstValue(data, v => v[0] === 'e', -3); expect(i).to.equal('egg'); }); it('should support searching within a range', () => { let data = ['dark', 'bottle', 'cat', 'dog', 'egg', 'dodge']; let i = ArrayExt.findFirstValue(data, v => v[0] === 'd', 2, 4); expect(i).to.equal('dog'); }); it('should support a negative stop index', () => { let data = ['dark', 'bottle', 'cat', 'dog', 'egg', 'dodge']; let i = ArrayExt.findFirstValue(data, v => v[0] === 'd', 2, -2); expect(i).to.equal('dog'); }); it('should wrap around if stop < start', () => { let data = ['dark', 'bottle', 'cat', 'dog', 'egg', 'dodge']; let i = ArrayExt.findFirstValue(data, v => v[0] === 'b', 4, 2); expect(i).to.equal('bottle'); }); }); describe('findLastValue()', () => { it('should find the index of the last matching value', () => { let data = ['apple', 'bottle', 'cat', 'dog', 'egg', 'blue']; let i = ArrayExt.findLastValue(data, v => v[0] === 'b'); expect(i).to.equal('blue'); }); it('should return `undefined` if there is no matching value', () => { let data = ['apple', 'bottle', 'cat', 'dog', 'egg', 'fish']; let i = ArrayExt.findLastValue(data, v => v[0] === 'z'); expect(i).to.equal(undefined); }); it('should return `undefined` if the array is empty', () => { let data: string[] = []; let i = ArrayExt.findLastValue(data, v => v[0] === 'b'); expect(i).to.equal(undefined); }); it('should support searching from a start index', () => { let data = ['apple', 'eagle', 'cat', 'dog', 'egg', 'fish']; let i = ArrayExt.findLastValue(data, v => v[0] === 'e', 2); expect(i).to.equal('eagle'); }); it('should support a negative start index', () => { let data = ['apple', 'eagle', 'cat', 'dog', 'egg', 'fish']; let i = ArrayExt.findLastValue(data, v => v[0] === 'e', -3); expect(i).to.equal('eagle'); }); it('should support searching within a range', () => { let data = ['dark', 'bottle', 'cat', 'dog', 'egg', 'dodge']; let i = ArrayExt.findLastValue(data, v => v[0] === 'd', 4, 2); expect(i).to.equal('dog'); }); it('should support a negative stop index', () => { let data = ['dark', 'bottle', 'cat', 'dog', 'egg', 'dodge']; let i = ArrayExt.findLastValue(data, v => v[0] === 'd', 4, -4); expect(i).to.equal('dog'); }); it('should wrap around if start < stop', () => { let data = ['dark', 'bottle', 'cat', 'dog', 'egg', 'dodge']; let i = ArrayExt.findLastValue(data, v => v[0] === 'e', 2, 4); expect(i).to.equal('egg'); }); }); describe('lowerBound()', () => { it('should return the index of the first element `>=` a value', () => { let data = [1, 2, 2, 3, 3, 4, 5, 5]; let cmp = (a: number, b: number) => a - b; let r1 = ArrayExt.lowerBound(data, -5, cmp); let r2 = ArrayExt.lowerBound(data, 0, cmp); let r3 = ArrayExt.lowerBound(data, 3, cmp); let r4 = ArrayExt.lowerBound(data, 5, cmp); expect(r1).to.equal(0); expect(r2).to.equal(0); expect(r3).to.equal(3); expect(r4).to.equal(6); }); it('should return `length` if there is no matching value', () => { let data = [1, 2, 2, 3, 3, 4, 5, 5]; let cmp = (a: number, b: number) => a - b; let r1 = ArrayExt.lowerBound(data, 9, cmp); let r2 = ArrayExt.lowerBound(data, 19, cmp); let r3 = ArrayExt.lowerBound(data, 29, cmp); expect(r1).to.equal(8); expect(r2).to.equal(8); expect(r3).to.equal(8); }); it('should return `0` if the array is empty', () => { let data: number[] = []; let cmp = (a: number, b: number) => a - b; let i = ArrayExt.lowerBound(data, 0, cmp); expect(i).to.equal(0); }); it('should support searching a range', () => { let data = [4, 5, 6, 4, 5, 6]; let cmp = (a: number, b: number) => a - b; let r = ArrayExt.lowerBound(data, 5, cmp, 3, 5); expect(r).to.equal(4); }); }); describe('upperBound()', () => { it('should return the index of the first element `>` a value', () => { let data = [1, 2, 2, 3, 3, 4, 5, 5]; let cmp = (a: number, b: number) => a - b; let r1 = ArrayExt.upperBound(data, -5, cmp); let r2 = ArrayExt.upperBound(data, 0, cmp); let r3 = ArrayExt.upperBound(data, 2, cmp); let r4 = ArrayExt.upperBound(data, 3, cmp); expect(r1).to.equal(0); expect(r2).to.equal(0); expect(r3).to.equal(3); expect(r4).to.equal(5); }); it('should return `length` if there is no matching value', () => { let data = [1, 2, 2, 3, 3, 4, 5, 5]; let cmp = (a: number, b: number) => a - b; let r1 = ArrayExt.upperBound(data, 9, cmp); let r2 = ArrayExt.upperBound(data, 19, cmp); let r3 = ArrayExt.upperBound(data, 29, cmp); expect(r1).to.equal(8); expect(r2).to.equal(8); expect(r3).to.equal(8); }); it('should return `0` if the array is empty', () => { let data: number[] = []; let cmp = (a: number, b: number) => a - b; let i = ArrayExt.upperBound(data, 0, cmp); expect(i).to.equal(0); }); it('should support searching a range', () => { let data = [4, 5, 6, 4, 5, 6]; let cmp = (a: number, b: number) => a - b; let r = ArrayExt.upperBound(data, 5, cmp, 3, 5); expect(r).to.equal(5); }); }); describe('move()', () => { it('should move an element from one index to another', () => { let data = [1, 2, 3, 4, 5]; ArrayExt.move(data, 1, 3); ArrayExt.move(data, 4, 0); expect(data).to.deep.equal([5, 1, 3, 4, 2]); }); it('should be a no-op for equal indices', () => { let data = [1, 2, 3, 4, 5]; ArrayExt.move(data, 2, 2); expect(data).to.deep.equal([1, 2, 3, 4, 5]); }); it('should be a no-op for an array length `<= 1`', () => { let data1 = [1]; let data2: any[] = []; ArrayExt.move(data1, 0, 0); ArrayExt.move(data2, 0, 0); expect(data1).to.deep.equal([1]); expect(data2).to.deep.equal([]); }); }); describe('reverse()', () => { it('should reverse an array in-place', () => { let data = [1, 2, 3, 4, 5]; ArrayExt.reverse(data); expect(data).to.deep.equal([5, 4, 3, 2, 1]); }); it('should support reversing a section of an array', () => { let data = [1, 2, 3, 4, 5]; ArrayExt.reverse(data, 2); expect(data).to.deep.equal([1, 2, 5, 4, 3]); ArrayExt.reverse(data, 0, 3); expect(data).to.deep.equal([4, 5, 2, 1, 3]); }); it('should be a no-op if `start >= stop`', () => { let data = [1, 2, 3, 4, 5]; ArrayExt.reverse(data, 2, 2); expect(data).to.deep.equal([1, 2, 3, 4, 5]); ArrayExt.reverse(data, 4, 2); expect(data).to.deep.equal([1, 2, 3, 4, 5]); }); it('should be a no-op for an array length `<= 1`', () => { let data1 = [1]; let data2: any[] = []; ArrayExt.reverse(data1); ArrayExt.reverse(data2); expect(data1).to.deep.equal([1]); expect(data2).to.deep.equal([]); }); }); describe('rotate()', () => { it('should rotate the elements left by a positive delta', () => { let data = [1, 2, 3, 4, 5]; ArrayExt.rotate(data, 2); expect(data).to.deep.equal([3, 4, 5, 1, 2]); ArrayExt.rotate(data, 12); expect(data).to.deep.equal([5, 1, 2, 3, 4]); }); it('should rotate the elements right by a negative delta', () => { let data = [1, 2, 3, 4, 5]; ArrayExt.rotate(data, -2); expect(data).to.deep.equal([4, 5, 1, 2, 3]); ArrayExt.rotate(data, -12); expect(data).to.deep.equal([2, 3, 4, 5, 1]); }); it('should be a no-op for a zero delta', () => { let data = [1, 2, 3, 4, 5]; ArrayExt.rotate(data, 0); expect(data).to.deep.equal([1, 2, 3, 4, 5]); }); it('should be a no-op for a array length `<= 1`', () => { let data1 = [1]; let data2: any[] = []; ArrayExt.rotate(data1, 1); ArrayExt.rotate(data2, 1); expect(data1).to.deep.equal([1]); expect(data2).to.deep.equal([]); }); it('should rotate a section of the array', () => { let data = [1, 2, 3, 4, 5]; ArrayExt.rotate(data, 2, 1, 3); expect(data).to.deep.equal([1, 4, 2, 3, 5]); ArrayExt.rotate(data, -2, 0, 3); expect(data).to.deep.equal([2, 3, 1, 4, 5]); }); it('should be a no-op if `start >= stop`', () => { let data = [1, 2, 3, 4, 5]; ArrayExt.rotate(data, 2, 5, 4); expect(data).to.deep.equal([1, 2, 3, 4, 5]); }); }); describe('fill()', () => { it('should fill an array with a static value', () => { let data = [0, 0, 0, 0, 0]; ArrayExt.fill(data, 1); expect(data).to.deep.equal([1, 1, 1, 1, 1]); }); it('should fill a section of the array', () => { let data = [0, 0, 0, 0, 0]; ArrayExt.fill(data, 1, 1, 3); expect(data).to.deep.equal([0, 1, 1, 1, 0]); }); it('should wrap around if `stop < start`', () => { let data = [0, 0, 0, 0, 0]; ArrayExt.fill(data, 1, 3, 1); expect(data).to.deep.equal([1, 1, 0, 1, 1]); }); }); describe('insert()', () => { it('should insert a value at the specified index', () => { let data: number[] = []; ArrayExt.insert(data, 0, 9); expect(data).to.deep.equal([9]); ArrayExt.insert(data, 0, 8); expect(data).to.deep.equal([8, 9]); ArrayExt.insert(data, 0, 7); expect(data).to.deep.equal([7, 8, 9]); ArrayExt.insert(data, -2, 6); expect(data).to.deep.equal([7, 6, 8, 9]); ArrayExt.insert(data, 2, 5); expect(data).to.deep.equal([7, 6, 5, 8, 9]); ArrayExt.insert(data, -5, 4); expect(data).to.deep.equal([4, 7, 6, 5, 8, 9]); }); it('should clamp the index to the bounds of the vector', () => { let data: number[] = []; ArrayExt.insert(data, -10, 9); expect(data).to.deep.equal([9]); ArrayExt.insert(data, -5, 8); expect(data).to.deep.equal([8, 9]); ArrayExt.insert(data, -1, 7); expect(data).to.deep.equal([8, 7, 9]); ArrayExt.insert(data, 13, 6); expect(data).to.deep.equal([8, 7, 9, 6]); ArrayExt.insert(data, 8, 4); expect(data).to.deep.equal([8, 7, 9, 6, 4]); }); }); describe('removeAt()', () => { it('should remove the value at a specified index', () => { let data = [7, 4, 8, 5, 9, 6]; expect(ArrayExt.removeAt(data, 1)).to.equal(4); expect(data).to.deep.equal([7, 8, 5, 9, 6]); expect(ArrayExt.removeAt(data, 2)).to.equal(5); expect(data).to.deep.equal([7, 8, 9, 6]); expect(ArrayExt.removeAt(data, -2)).to.equal(9); expect(data).to.deep.equal([7, 8, 6]); expect(ArrayExt.removeAt(data, 0)).to.equal(7); expect(data).to.deep.equal([8, 6]); expect(ArrayExt.removeAt(data, -1)).to.equal(6); expect(data).to.deep.equal([8]); expect(ArrayExt.removeAt(data, 0)).to.equal(8); expect(data).to.deep.equal([]); }); it('should return `undefined` if the index is out of range', () => { let data = [7, 4, 8, 5, 9, 6]; expect(ArrayExt.removeAt(data, 10)).to.equal(undefined); expect(data).to.deep.equal([7, 4, 8, 5, 9, 6]); expect(ArrayExt.removeAt(data, -12)).to.equal(undefined); expect(data).to.deep.equal([7, 4, 8, 5, 9, 6]); }); }); describe('removeFirstOf()', () => { it('should remove the first occurrence of a value', () => { let data = ['one', 'two', 'three', 'four', 'one']; let i = ArrayExt.removeFirstOf(data, 'one'); expect(i).to.equal(0); expect(data).to.deep.equal(['two', 'three', 'four', 'one']); }); it('should return `-1` if there is no matching value', () => { let data = ['one', 'two', 'three', 'four', 'one']; let i = ArrayExt.removeFirstOf(data, 'five'); expect(i).to.equal(-1); expect(data).to.deep.equal(['one', 'two', 'three', 'four', 'one']); }); it('should return `-1` if the array is empty', () => { let data: string[] = []; let i = ArrayExt.removeFirstOf(data, 'five'); expect(i).to.equal(-1); expect(data).to.deep.equal([]); }); it('should support searching from a start index', () => { let data = ['one', 'two', 'three', 'four', 'one']; let i = ArrayExt.removeFirstOf(data, 'one', 2); expect(i).to.equal(4); expect(data).to.deep.equal(['one', 'two', 'three', 'four']); }); it('should support a negative start index', () => { let data = ['one', 'two', 'three', 'four', 'one']; let i = ArrayExt.removeFirstOf(data, 'one', -2); expect(i).to.equal(4); expect(data).to.deep.equal(['one', 'two', 'three', 'four']); }); it('should support searching within a range', () => { let data = ['three', 'two', 'three', 'four', 'one']; let i = ArrayExt.removeFirstOf(data, 'three', 1, 3); expect(i).to.equal(2); expect(data).to.deep.equal(['three', 'two', 'four', 'one']); }); it('should support a negative stop index', () => { let data = ['three', 'two', 'three', 'four', 'three']; let i = ArrayExt.removeFirstOf(data, 'three', 1, -2); expect(i).to.equal(2); expect(data).to.deep.equal(['three', 'two', 'four', 'three']); }); it('should wrap around if stop < start', () => { let data = ['one', 'two', 'three', 'four', 'one']; let i = ArrayExt.removeFirstOf(data, 'two', 3, 1); expect(i).to.equal(1); expect(data).to.deep.equal(['one', 'three', 'four', 'one']); }); }); describe('removeLastOf()', () => { it('should remove the last occurrence of a value', () => { let data = ['one', 'two', 'three', 'four', 'one']; let i = ArrayExt.removeLastOf(data, 'one'); expect(i).to.equal(4); expect(data).to.deep.equal(['one', 'two', 'three', 'four']); }); it('should return `-1` if there is no matching value', () => { let data = ['one', 'two', 'three', 'four', 'one']; let i = ArrayExt.removeLastOf(data, 'five'); expect(i).to.equal(-1); expect(data).to.deep.equal(['one', 'two', 'three', 'four', 'one']); }); it('should return `-1` if the array is empty', () => { let data: string[] = []; let i = ArrayExt.removeLastOf(data, 'five'); expect(i).to.equal(-1); expect(data).to.deep.equal([]); }); it('should support searching from a start index', () => { let data = ['one', 'two', 'three', 'four', 'one']; let i = ArrayExt.removeLastOf(data, 'one', 2); expect(i).to.equal(0); expect(data).to.deep.equal(['two', 'three', 'four', 'one']); }); it('should support a negative start index', () => { let data = ['one', 'two', 'three', 'four', 'one']; let i = ArrayExt.removeLastOf(data, 'one', -2); expect(i).to.equal(0); expect(data).to.deep.equal(['two', 'three', 'four', 'one']); }); it('should support searching within a range', () => { let data = ['three', 'two', 'three', 'four', 'one']; let i = ArrayExt.removeLastOf(data, 'three', 3, 1); expect(i).to.equal(2); expect(data).to.deep.equal(['three', 'two', 'four', 'one']); }); it('should support a negative stop index', () => { let data = ['three', 'two', 'three', 'four', 'three']; let i = ArrayExt.removeLastOf(data, 'three', 3, -4); expect(i).to.equal(2); expect(data).to.deep.equal(['three', 'two', 'four', 'three']); }); it('should wrap around if start < stop', () => { let data = ['one', 'two', 'three', 'four', 'one']; let i = ArrayExt.removeLastOf(data, 'two', 3, 1); expect(i).to.equal(1); expect(data).to.deep.equal(['one', 'three', 'four', 'one']); }); }); describe('removeAllOf()', () => { it('should remove all occurrences of a value', () => { let data = ['one', 'two', 'three', 'four', 'one']; let i = ArrayExt.removeAllOf(data, 'one'); expect(i).to.equal(2); expect(data).to.deep.equal(['two', 'three', 'four']); }); it('should return `0` if there is no matching value', () => { let data = ['one', 'two', 'three', 'four', 'one']; let i = ArrayExt.removeAllOf(data, 'five'); expect(i).to.equal(0); expect(data).to.deep.equal(['one', 'two', 'three', 'four', 'one']); }); it('should return `0` if the array is empty', () => { let data: string[] = []; let i = ArrayExt.removeAllOf(data, 'five'); expect(i).to.equal(0); expect(data).to.deep.equal([]); }); it('should support searching from a start index', () => { let data = ['one', 'two', 'three', 'four', 'one']; let i = ArrayExt.removeAllOf(data, 'one', 2); expect(i).to.equal(1); expect(data).to.deep.equal(['one', 'two', 'three', 'four']); }); it('should support a negative start index', () => { let data = ['one', 'two', 'three', 'four', 'one']; let i = ArrayExt.removeAllOf(data, 'one', -2); expect(i).to.equal(1); expect(data).to.deep.equal(['one', 'two', 'three', 'four']); }); it('should support searching within a range', () => { let data = ['three', 'two', 'three', 'four', 'one']; let i = ArrayExt.removeAllOf(data, 'three', 1, 3); expect(i).to.equal(1); expect(data).to.deep.equal(['three', 'two', 'four', 'one']); }); it('should support a negative stop index', () => { let data = ['three', 'two', 'three', 'four', 'three']; let i = ArrayExt.removeAllOf(data, 'three', 1, -2); expect(i).to.equal(1); expect(data).to.deep.equal(['three', 'two', 'four', 'three']); }); it('should wrap around if start < stop', () => { let data = ['one', 'two', 'three', 'four', 'one']; let i = ArrayExt.removeAllOf(data, 'one', 3, 1); expect(i).to.equal(2); expect(data).to.deep.equal(['two', 'three', 'four']); }); }); describe('removeFirstWhere()', () => { it('should remove the first occurrence of a value', () => { let data = [1, 2, 3, 4, 5]; let result = ArrayExt.removeFirstWhere(data, v => v % 2 === 0); expect(result.index).to.equal(1); expect(result.value).to.equal(2); expect(data).to.deep.equal([1, 3, 4, 5]); }); it('should return `-1` if there is no matching value', () => { let data = [1, 2, 3, 4, 5]; let result = ArrayExt.removeFirstWhere(data, v => v % 7 === 0); expect(result.index).to.equal(-1); expect(result.value).to.equal(undefined); expect(data).to.deep.equal([1, 2, 3, 4, 5]); }); it('should return `-1` if the array is empty', () => { let data: number[] = []; let result = ArrayExt.removeFirstWhere(data, v => v % 7 === 0); expect(result.index).to.equal(-1); expect(result.value).to.equal(undefined); expect(data).to.deep.equal([]); }); it('should support searching from a start index', () => { let data = [1, 2, 3, 4, 5]; let result = ArrayExt.removeFirstWhere(data, v => v % 2 === 0, 2); expect(result.index).to.equal(3); expect(result.value).to.equal(4); expect(data).to.deep.equal([1, 2, 3, 5]); }); it('should support a negative start index', () => { let data = [1, 2, 3, 4, 5]; let result = ArrayExt.removeFirstWhere(data, v => v % 2 === 0, -3); expect(result.index).to.equal(3); expect(result.value).to.equal(4); expect(data).to.deep.equal([1, 2, 3, 5]); }); it('should support searching within a range', () => { let data = [1, 2, 3, 4, 5]; let result = ArrayExt.removeFirstWhere(data, v => v % 2 === 0, 2, 4); expect(result.index).to.equal(3); expect(result.value).to.equal(4); expect(data).to.deep.equal([1, 2, 3, 5]); }); it('should support a negative stop index', () => { let data = [1, 2, 3, 4, 5]; let result = ArrayExt.removeFirstWhere(data, v => v % 2 === 0, 2, -2); expect(result.index).to.equal(3); expect(result.value).to.equal(4); expect(data).to.deep.equal([1, 2, 3, 5]); }); it('should wrap around if stop < start', () => { let data = [1, 2, 3, 4, 5]; let result = ArrayExt.removeFirstWhere(data, v => v % 2 === 0, 4, 2); expect(result.index).to.equal(1); expect(result.value).to.equal(2); expect(data).to.deep.equal([1, 3, 4, 5]); }); }); describe('removeLastWhere()', () => { it('should remove the last occurrence of a value', () => { let data = [1, 2, 3, 4, 5]; let result = ArrayExt.removeLastWhere(data, v => v % 2 === 0); expect(result.index).to.equal(3); expect(result.value).to.equal(4); expect(data).to.deep.equal([1, 2, 3, 5]); }); it('should return `-1` if there is no matching value', () => { let data = [1, 2, 3, 4, 5]; let result = ArrayExt.removeLastWhere(data, v => v % 7 === 0); expect(result.index).to.equal(-1); expect(result.value).to.equal(undefined); expect(data).to.deep.equal([1, 2, 3, 4, 5]); }); it('should return `-1` if the array is empty', () => { let data: number[] = []; let result = ArrayExt.removeLastWhere(data, v => v % 7 === 0); expect(result.index).to.equal(-1); expect(result.value).to.equal(undefined); expect(data).to.deep.equal([]); }); it('should support searching from a start index', () => { let data = [1, 2, 3, 4, 5]; let result = ArrayExt.removeLastWhere(data, v => v % 2 === 0, 2); expect(result.index).to.equal(1); expect(result.value).to.equal(2); expect(data).to.deep.equal([1, 3, 4, 5]); }); it('should support a negative start index', () => { let data = [1, 2, 3, 4, 5]; let result = ArrayExt.removeLastWhere(data, v => v % 2 === 0, -3); expect(result.index).to.equal(1); expect(result.value).to.equal(2); expect(data).to.deep.equal([1, 3, 4, 5]); }); it('should support searching within a range', () => { let data = [1, 2, 3, 4, 5]; let result = ArrayExt.removeLastWhere(data, v => v % 2 === 0, 4, 2); expect(result.index).to.equal(3); expect(result.value).to.equal(4); expect(data).to.deep.equal([1, 2, 3, 5]); }); it('should support a negative stop index', () => { let data = [1, 2, 3, 4, 5]; let result = ArrayExt.removeLastWhere(data, v => v % 2 === 0, 4, -4); expect(result.index).to.equal(3); expect(result.value).to.equal(4); expect(data).to.deep.equal([1, 2, 3, 5]); }); it('should wrap around if start < stop', () => { let data = [1, 2, 3, 4, 5]; let result = ArrayExt.removeLastWhere(data, v => v % 2 === 0, 0, 2); expect(result.index).to.equal(3); expect(result.value).to.equal(4); expect(data).to.deep.equal([1, 2, 3, 5]); }); }); describe('removeAllWhere()', () => { it('should remove all occurrences of a value', () => { let data = [1, 2, 3, 4, 3, 5, 1]; let count = ArrayExt.removeAllWhere(data, v => v % 3 === 0); expect(count).to.equal(2); expect(data).to.deep.equal([1, 2, 4, 5, 1]); }); it('should return `0` if there is no matching value', () => { let data = [1, 2, 3, 4, 3, 5, 1]; let count = ArrayExt.removeAllWhere(data, v => v % 7 === 0); expect(count).to.equal(0); expect(data).to.deep.equal([1, 2, 3, 4, 3, 5, 1]); }); it('should return `0` if the array is empty', () => { let data: number[] = []; let count = ArrayExt.removeAllWhere(data, v => v % 7 === 0); expect(count).to.equal(0); expect(data).to.deep.equal([]); }); it('should support searching from a start index', () => { let data = [1, 2, 3, 4, 3, 5, 1]; let count = ArrayExt.removeAllWhere(data, v => v % 3 === 0, 3); expect(count).to.equal(1); expect(data).to.deep.equal([1, 2, 3, 4, 5, 1]); }); it('should support a negative start index', () => { let data = [1, 2, 3, 4, 3, 5, 1]; let count = ArrayExt.removeAllWhere(data, v => v % 3 === 0, -4); expect(count).to.equal(1); expect(data).to.deep.equal([1, 2, 3, 4, 5, 1]); }); it('should support searching within a range', () => { let data = [1, 2, 3, 4, 3, 5, 1]; let count = ArrayExt.removeAllWhere(data, v => v % 3 === 0, 3, 5); expect(count).to.equal(1); expect(data).to.deep.equal([1, 2, 3, 4, 5, 1]); }); it('should support a negative stop index', () => { let data = [1, 2, 3, 4, 3, 5, 1]; let count = ArrayExt.removeAllWhere(data, v => v % 3 === 0, 3, -2); expect(count).to.equal(1); expect(data).to.deep.equal([1, 2, 3, 4, 5, 1]); }); it('should wrap around if start < stop', () => { let data = [1, 2, 3, 4, 3, 5, 1]; let count = ArrayExt.removeAllWhere(data, v => v % 3 === 0, 5, 3); expect(count).to.equal(1); expect(data).to.deep.equal([1, 2, 4, 3, 5, 1]); }); }); }); }); lumino-2021.12.13/packages/algorithm/tests/src/chain.spec.ts000066400000000000000000000022031415564225700234760ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { chain, ChainIterator, iter } from '@lumino/algorithm'; import { testIterator } from './iter.spec'; describe('@lumino/algorithm', () => { describe('chain()', () => { testIterator(() => { let it = chain([1, 2, 3], [4], [5, 6]); let expected = [1, 2, 3, 4, 5, 6]; return [it, expected]; }); }); describe('ChainIterator', () => { testIterator(() => { let a = iter([1, 2, 3]); let b = iter(['four', 'five']); let c = iter([true, false]); type T = number | string | boolean; let it = new ChainIterator(iter([a, b, c])); let expected = [1, 2, 3, 'four', 'five', true, false]; return [it, expected]; }); }); }); lumino-2021.12.13/packages/algorithm/tests/src/empty.spec.ts000066400000000000000000000014531415564225700235600ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { empty, EmptyIterator } from '@lumino/algorithm'; import { testIterator } from './iter.spec'; describe('@lumino/algorithm', () => { describe('empty()', () => { testIterator(() => { return [empty(), []]; }); }); describe('EmptyIterator', () => { testIterator(() => { return [new EmptyIterator(), []]; }); }); }); lumino-2021.12.13/packages/algorithm/tests/src/filter.spec.ts000066400000000000000000000020451415564225700237050ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { filter, FilterIterator, iter } from '@lumino/algorithm'; import { testIterator } from './iter.spec'; describe('@lumino/algorithm', () => { describe('filter()', () => { testIterator(() => { let expected = [0, 2, 4]; let data = [0, 1, 2, 3, 4, 5]; let it = filter(data, n => n % 2 === 0); return [it, expected]; }); }); describe('FilterIterator', () => { testIterator(() => { let expected = [1, 3, 5]; let data = [0, 1, 2, 3, 4, 5]; let it = new FilterIterator(iter(data), n => n % 2 !== 0); return [it, expected]; }); }); }); lumino-2021.12.13/packages/algorithm/tests/src/find.spec.ts000066400000000000000000000112231415564225700233360ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { expect } from 'chai'; import { find, max, min, minmax } from '@lumino/algorithm'; describe('@lumino/algorithm', () => { describe('find()', () => { it('should find the first matching value', () => { interface IAnimal { species: string; name: string; } let isCat = (value: IAnimal) => value.species === 'cat'; let data: IAnimal[] = [ { species: 'dog', name: 'spot' }, { species: 'cat', name: 'fluffy' }, { species: 'alligator', name: 'pocho' } ]; expect(find(data, isCat)).to.equal(data[1]); }); it('should return `undefined` if there is no matching value', () => { interface IAnimal { species: string; name: string; } let isRacoon = (value: IAnimal) => value.species === 'racoon'; let data: IAnimal[] = [ { species: 'dog', name: 'spot' }, { species: 'cat', name: 'fluffy' }, { species: 'alligator', name: 'pocho' } ]; expect(find(data, isRacoon)).to.equal(undefined); }); }); describe('min()', () => { it('should return the minimum value in an iterable', () => { interface IScore { value: number; } let data: IScore[] = [ { value: 19 }, { value: -2 }, { value: 0 }, { value: 42 } ]; let score = min(data, (a, b) => a.value - b.value); expect(score).to.equal(data[1]); }); it('should not invoke the comparator for only one value', () => { interface IScore { value: number; } let data: IScore[] = [{ value: 19 }]; let called = false; let score = min(data, (a, b) => { called = true; return a.value - b.value; }); expect(score).to.equal(data[0]); expect(called).to.equal(false); }); it('should return `undefined` if the iterable is empty', () => { interface IScore { value: number; } let data: IScore[] = []; let score = min(data, (a, b) => a.value - b.value); expect(score).to.equal(undefined); }); }); describe('max()', () => { it('should return the maximum value in an iterable', () => { interface IScore { value: number; } let data: IScore[] = [ { value: 19 }, { value: -2 }, { value: 0 }, { value: 42 } ]; let score = max(data, (a, b) => a.value - b.value); expect(score).to.equal(data[3]); }); it('should not invoke the comparator for only one value', () => { interface IScore { value: number; } let data: IScore[] = [{ value: 19 }]; let called = false; let score = max(data, (a, b) => { called = true; return a.value - b.value; }); expect(score).to.equal(data[0]); expect(called).to.equal(false); }); it('should return `undefined` if the iterable is empty', () => { interface IScore { value: number; } let data: IScore[] = []; let score = max(data, (a, b) => a.value - b.value); expect(score).to.equal(undefined); }); }); describe('minmax()', () => { it('should return the minimum and maximum value in an iterable', () => { interface IScore { value: number; } let data: IScore[] = [ { value: 19 }, { value: -2 }, { value: 0 }, { value: 42 } ]; let [rmin, rmax] = minmax(data, (a, b) => a.value - b.value)!; expect(rmin).to.equal(data[1]); expect(rmax).to.equal(data[3]); }); it('should not invoke the comparator for only one value', () => { interface IScore { value: number; } let data: IScore[] = [{ value: 19 }]; let called = false; let [rmin, rmax] = minmax(data, (a, b) => { called = true; return a.value - b.value; })!; expect(rmin).to.equal(data[0]); expect(rmax).to.equal(data[0]); expect(called).to.equal(false); }); it('should return `undefined` if the iterable is empty', () => { interface IScore { value: number; } let data: IScore[] = []; let score = minmax(data, (a, b) => a.value - b.value); expect(score).to.equal(undefined); }); }); }); lumino-2021.12.13/packages/algorithm/tests/src/index.spec.ts000066400000000000000000000014611415564225700235300ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2016, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import './array.spec'; import './chain.spec'; import './empty.spec'; import './filter.spec'; import './find.spec'; import './iter.spec'; import './map.spec'; import './range.spec'; import './reduce.spec'; import './repeat.spec'; import './retro.spec'; import './sort.spec'; import './stride.spec'; import './string.spec'; import './take.spec'; import './zip.spec'; lumino-2021.12.13/packages/algorithm/tests/src/iter.spec.ts000066400000000000000000000072431415564225700233700ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { expect } from 'chai'; import { ArrayIterator, each, every, IIterator, iter, some, toArray } from '@lumino/algorithm'; /** * A factory which returns an iterator and expected results. */ export type IteratorFactory = () => [IIterator, T[]]; /** * A helper function to test the methods of an iterator. * * @param factory - A function which produces an iterator and the * expected results of that iterator. */ export function testIterator(factory: IteratorFactory): void { describe('iter()', () => { it('should return `this` iterator', () => { let [it] = factory(); expect(it.iter()).to.equal(it); }); }); describe('clone()', () => { it('should return a new independent iterator', () => { let [it, results] = factory(); let it2 = it.clone(); expect(it).to.not.equal(it2); expect(toArray(it)).to.deep.equal(results); expect(toArray(it2)).to.deep.equal(results); }); }); describe('next()', () => { it('should return the next value in the iterator', () => { let value: T | undefined; let [it, results] = factory(); for (let i = 0; (value = it.next()) !== undefined; ++i) { expect(value).to.deep.equal(results[i]); } }); }); } describe('@lumino/algorithm', () => { describe('iter()', () => { it('should create an iterator for an array-like object', () => { let data = [0, 1, 2, 3]; expect(toArray(iter(data))).to.deep.equal(data); }); it('should call `iter` on an iterable', () => { let iterator = iter([1, 2, 3, 4]); expect(iter(iterator)).to.equal(iterator); }); }); describe('each()', () => { it('should visit every item in an iterable', () => { let result = 0; let data = [1, 2, 3, 4, 5]; each(data, x => { result += x; }); expect(result).to.equal(15); }); it('should break early if the callback returns `false`', () => { let result = 0; let data = [1, 2, 3, 4, 5]; each(data, x => { if (x > 3) { return false; } result += x; return true; }); expect(result).to.equal(6); }); }); describe('every()', () => { it('should verify all items in an iterable satisfy a condition', () => { let data = [1, 2, 3, 4, 5]; let valid = every(data, x => x > 0); let invalid = every(data, x => x > 4); expect(valid).to.equal(true); expect(invalid).to.equal(false); }); }); describe('some()', () => { it('should verify some items in an iterable satisfy a condition', () => { let data = [1, 2, 3, 4, 5]; let valid = some(data, x => x > 4); let invalid = some(data, x => x < 0); expect(valid).to.equal(true); expect(invalid).to.equal(false); }); }); describe('toArray()', () => { it('should create an array from an iterable', () => { let data = [0, 1, 2, 3, 4, 5]; let result = toArray(data); expect(result).to.deep.equal(data); expect(result).to.not.equal(data); }); }); describe('ArrayIterator', () => { testIterator(() => { let results = [1, 2, 3, 4, 5]; let it = new ArrayIterator(results); return [it, results]; }); }); }); lumino-2021.12.13/packages/algorithm/tests/src/map.spec.ts000066400000000000000000000017341415564225700232010ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { iter, map, MapIterator } from '@lumino/algorithm'; import { testIterator } from './iter.spec'; describe('@lumino/algorithm', () => { describe('map()', () => { testIterator(() => { let result = [0, 1, 4, 9, 16, 25]; let it = map([0, 1, 2, 3, 4, 5], x => x ** 2); return [it, result]; }); }); describe('MapIterator', () => { testIterator(() => { let result = [0, 1, 8, 27]; let it = new MapIterator(iter([0, 1, 2, 3]), x => x ** 3); return [it, result]; }); }); }); lumino-2021.12.13/packages/algorithm/tests/src/range.spec.ts000066400000000000000000000026241415564225700235170ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { range, RangeIterator } from '@lumino/algorithm'; import { testIterator } from './iter.spec'; describe('@lumino/algorithm', () => { describe('range()', () => { describe('single argument form', () => { testIterator(() => { return [range(3), [0, 1, 2]]; }); }); describe('two argument form', () => { testIterator(() => { return [range(4, 7), [4, 5, 6]]; }); }); describe('three argument form', () => { testIterator(() => { return [range(4, 11, 3), [4, 7, 10]]; }); }); describe('negative step', () => { testIterator(() => { return [range(3, 0, -1), [3, 2, 1]]; }); }); describe('zero effective length', () => { testIterator(() => { return [range(0, 10, -1), []]; }); }); }); describe('RangeIterator', () => { testIterator(() => { return [new RangeIterator(0, 11, 2), [0, 2, 4, 6, 8, 10]]; }); }); }); lumino-2021.12.13/packages/algorithm/tests/src/reduce.spec.ts000066400000000000000000000035271415564225700236750ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { expect } from 'chai'; import { reduce } from '@lumino/algorithm'; describe('@lumino/algorithm', () => { describe('reduce()', () => { it('should reduce items in an iterable into an accumulated value', () => { let sum = reduce([1, 2, 3, 4, 5], (a, x) => a + x, 0); expect(sum).to.equal(15); }); it('should throw if iterable is empty and initial value is undefined', () => { let data: Array = []; let reduced = () => reduce(data, (a, x) => a + x); expect(reduced).to.throw(TypeError); }); it('should return the initial value if the iterable is empty', () => { let data: Array = []; let result = reduce(data, (a, x) => a + x, 0); expect(result).to.equal(0); }); it('should return the first item if the iterable has just one item with no initial value', () => { let data = [9]; let result = reduce(data, (a, x) => a + x); expect(result).to.equal(9); }); it('should invoke the reducer if the iterable has just one item with an initial value', () => { let data = [9]; let result = reduce(data, (a, x) => a + x, 1); expect(result).to.equal(10); }); it('should invoke the reducer if the iterable has just two items with no initial value', () => { let data = [1, 2]; let result = reduce(data, (a, x) => a + x); expect(result).to.equal(3); }); }); }); lumino-2021.12.13/packages/algorithm/tests/src/repeat.spec.ts000066400000000000000000000017271415564225700237060ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { once, repeat, RepeatIterator } from '@lumino/algorithm'; import { testIterator } from './iter.spec'; describe('@lumino/algorithm', () => { describe('repeat()', () => { testIterator(() => { return [repeat('foo', 3), ['foo', 'foo', 'foo']]; }); }); describe('once()', () => { testIterator(() => { return [once('foo'), ['foo']]; }); }); describe('RepeatIterator', () => { testIterator(() => { return [new RepeatIterator('foo', 3), ['foo', 'foo', 'foo']]; }); }); }); lumino-2021.12.13/packages/algorithm/tests/src/retro.spec.ts000066400000000000000000000022341415564225700235530ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { expect } from 'chai'; import { iter, retro, RetroArrayIterator, toArray } from '@lumino/algorithm'; import { testIterator } from './iter.spec'; describe('@lumino/algorithm', () => { describe('retro()', () => { it('should create an iterator for an array-like object', () => { expect(toArray(retro([0, 1, 2, 3]))).to.deep.equal([3, 2, 1, 0]); }); it('should call `retro` on a retroable', () => { let iterator = iter([1, 2, 3, 4]); let retroable = { retro: () => iterator }; expect(retro(retroable)).to.equal(iterator); }); }); describe('RetroArrayIterator', () => { testIterator(() => { return [new RetroArrayIterator([1, 2, 3]), [3, 2, 1]]; }); }); }); lumino-2021.12.13/packages/algorithm/tests/src/sort.spec.ts000066400000000000000000000044111415564225700234060ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { expect } from 'chai'; import { topologicSort } from '@lumino/algorithm'; describe('@lumino/algorithm', () => { describe('topologicSort()', () => { it('should correctly order the input', () => { let data: Array<[string, string]> = [ ['a', 'b'], ['b', 'c'], ['c', 'd'], ['d', 'e'] ]; let result = topologicSort(data); expect(result).to.deep.equal(['a', 'b', 'c', 'd', 'e']); }); it('should correctly order shuffled input', () => { let data: Array<[string, string]> = [ ['d', 'e'], ['c', 'd'], ['a', 'b'], ['b', 'c'] ]; let result = topologicSort(data); expect(result).to.deep.equal(['a', 'b', 'c', 'd', 'e']); }); it('should return an approximate order when a cycle is present', () => { let data: Array<[string, string]> = [ ['a', 'b'], ['b', 'c'], ['c', 'd'], ['c', 'b'], ['d', 'e'] ]; let result = topologicSort(data); expect(result.indexOf('a')).to.equal(0); expect(result.indexOf('e')).to.equal(4); expect(result.indexOf('b')).to.be.greaterThan(0).lessThan(4); expect(result.indexOf('c')).to.be.greaterThan(0).lessThan(4); expect(result.indexOf('d')).to.be.greaterThan(0).lessThan(4); }); it('should return a valid order when under-constrained', () => { let data: Array<[string, string]> = [ ['a', 'b'], ['a', 'c'], ['a', 'd'], ['a', 'e'] ]; let result = topologicSort(data); expect(result.indexOf('a')).to.equal(0); expect(result.indexOf('b')).to.be.greaterThan(0); expect(result.indexOf('c')).to.be.greaterThan(0); expect(result.indexOf('d')).to.be.greaterThan(0); expect(result.indexOf('e')).to.be.greaterThan(0); }); }); }); lumino-2021.12.13/packages/algorithm/tests/src/stride.spec.ts000066400000000000000000000016131415564225700237120ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { iter, stride, StrideIterator } from '@lumino/algorithm'; import { testIterator } from './iter.spec'; describe('@lumino/algorithm', () => { describe('stride()', () => { testIterator(() => { return [stride([0, 1, 2, 3, 4, 5], 2), [0, 2, 4]]; }); }); describe('StrideIterator', () => { testIterator(() => { let it = iter([1, 2, 3, 4, 5, 6, 7]); return [new StrideIterator(it, 3), [1, 4, 7]]; }); }); }); lumino-2021.12.13/packages/algorithm/tests/src/string.spec.ts000066400000000000000000000102771415564225700237340ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { expect } from 'chai'; import { StringExt } from '@lumino/algorithm'; describe('@lumino/algorithm', () => { describe('StringExt', () => { describe('findIndices()', () => { it('should find the indices of the matching characters', () => { let r1 = StringExt.findIndices('Foo Bar Baz', 'Faa')!; let r2 = StringExt.findIndices('Foo Bar Baz', 'oBz')!; let r3 = StringExt.findIndices('Foo Bar Baz', 'r B')!; expect(r1).to.deep.equal([0, 5, 9]); expect(r2).to.deep.equal([1, 4, 10]); expect(r3).to.deep.equal([6, 7, 8]); }); it('should return `null` if no match is found', () => { let r1 = StringExt.findIndices('Foo Bar Baz', 'faa'); let r2 = StringExt.findIndices('Foo Bar Baz', 'obz'); let r3 = StringExt.findIndices('Foo Bar Baz', 'raB'); expect(r1).to.equal(null); expect(r2).to.equal(null); expect(r3).to.equal(null); }); }); describe('matchSumOfSquares()', () => { it('should score the match using the sum of squared distances', () => { let r1 = StringExt.matchSumOfSquares('Foo Bar Baz', 'Faa')!; let r2 = StringExt.matchSumOfSquares('Foo Bar Baz', 'oBz')!; let r3 = StringExt.matchSumOfSquares('Foo Bar Baz', 'r B')!; expect(r1.score).to.equal(106); expect(r1.indices).to.deep.equal([0, 5, 9]); expect(r2.score).to.equal(117); expect(r2.indices).to.deep.equal([1, 4, 10]); expect(r3.score).to.equal(149); expect(r3.indices).to.deep.equal([6, 7, 8]); }); it('should return `null` if no match is found', () => { let r1 = StringExt.matchSumOfSquares('Foo Bar Baz', 'faa'); let r2 = StringExt.matchSumOfSquares('Foo Bar Baz', 'obz'); let r3 = StringExt.matchSumOfSquares('Foo Bar Baz', 'raB'); expect(r1).to.equal(null); expect(r2).to.equal(null); expect(r3).to.equal(null); }); }); describe('matchSumOfDeltas()', () => { it('should score the match using the sum of deltas distances', () => { let r1 = StringExt.matchSumOfDeltas('Foo Bar Baz', 'Frz')!; let r2 = StringExt.matchSumOfDeltas('Foo Bar Baz', 'rBa')!; let r3 = StringExt.matchSumOfDeltas('Foo Bar Baz', 'oar')!; expect(r1.score).to.equal(8); expect(r1.indices).to.deep.equal([0, 6, 10]); expect(r2.score).to.equal(7); expect(r2.indices).to.deep.equal([6, 8, 9]); expect(r3.score).to.equal(4); expect(r3.indices).to.deep.equal([1, 5, 6]); }); it('should return `null` if no match is found', () => { let r1 = StringExt.matchSumOfDeltas('Foo Bar Baz', 'cce'); let r2 = StringExt.matchSumOfDeltas('Foo Bar Baz', 'ar3'); let r3 = StringExt.matchSumOfDeltas('Foo Bar Baz', 'raB'); expect(r1).to.equal(null); expect(r2).to.equal(null); expect(r3).to.equal(null); }); }); describe('highlight()', () => { it('should interpolate text with highlight results', () => { let mark = (chunk: string) => `${chunk}`; let r1 = StringExt.findIndices('Foo Bar Baz', 'Faa')!; let r2 = StringExt.findIndices('Foo Bar Baz', 'oBz')!; let r3 = StringExt.findIndices('Foo Bar Baz', 'r B')!; let h1 = StringExt.highlight('Foo Bar Baz', r1, mark).join(''); let h2 = StringExt.highlight('Foo Bar Baz', r2, mark).join(''); let h3 = StringExt.highlight('Foo Bar Baz', r3, mark).join(''); expect(h1).to.equal( 'Foo Bar Baz' ); expect(h2).to.equal( 'Foo Bar Baz' ); expect(h3).to.equal('Foo Bar Baz'); }); }); }); }); lumino-2021.12.13/packages/algorithm/tests/src/take.spec.ts000066400000000000000000000015271415564225700233500ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { iter, take, TakeIterator } from '@lumino/algorithm'; import { testIterator } from './iter.spec'; describe('@lumino/algorithm', () => { describe('take()', () => { testIterator(() => { return [take([1, 2, 3, 4, 5], 2), [1, 2]]; }); }); describe('TakeIterator', () => { testIterator(() => { return [new TakeIterator(iter([0, 1, 2, 3]), 1), [0]]; }); }); }); lumino-2021.12.13/packages/algorithm/tests/src/zip.spec.ts000066400000000000000000000022311415564225700232170ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { iter, zip, ZipIterator } from '@lumino/algorithm'; import { testIterator } from './iter.spec'; describe('@lumino/algorithm', () => { describe('zip()', () => { testIterator(() => { return [ zip([1, 2, 3], [4, 5, 6]), [ [1, 4], [2, 5], [3, 6] ] ]; }); }); describe('ZipIterator', () => { testIterator(() => { let i1 = iter(['one', 'two']); let i2 = iter([1, 2]); let i3 = iter([true, false]); type T = string | number | boolean; let it = new ZipIterator([i1, i2, i3]); let results = [ ['one', 1, true], ['two', 2, false] ]; return [it, results]; }); }); }); lumino-2021.12.13/packages/algorithm/tests/tsconfig.json000066400000000000000000000005601415564225700230370ustar00rootroot00000000000000{ "compilerOptions": { "declaration": false, "noImplicitAny": true, "noEmitOnError": true, "noUnusedLocals": true, "strictNullChecks": true, "module": "commonjs", "moduleResolution": "node", "target": "ES5", "outDir": "build", "lib": ["ES5"], "types": ["chai", "mocha"], "rootDir": "src" }, "include": ["src/*"] } lumino-2021.12.13/packages/algorithm/tests/webpack.config.js000066400000000000000000000003061415564225700235440ustar00rootroot00000000000000var path = require('path'); module.exports = { entry: './build/index.spec.js', mode: 'development', output: { filename: './build/bundle.test.js', path: path.resolve(__dirname) } }; lumino-2021.12.13/packages/algorithm/tsconfig.json000066400000000000000000000010071415564225700216720ustar00rootroot00000000000000{ "compilerOptions": { "composite": true, "sourceMap": true, "declaration": true, "declarationDir": "./types", "declarationMap": true, "noImplicitAny": true, "noEmitOnError": true, "noUnusedLocals": true, "strictNullChecks": true, "module": "ES6", "moduleResolution": "node", "target": "ES5", "outDir": "lib", "lib": ["ES5", "ES2015.Collection", "ES2015.Iterable"], "importHelpers": true, "types": [], "rootDir": "src" }, "include": ["src/*"] } lumino-2021.12.13/packages/application/000077500000000000000000000000001415564225700175025ustar00rootroot00000000000000lumino-2021.12.13/packages/application/api-extractor.json000066400000000000000000000014611415564225700231610ustar00rootroot00000000000000/** * Config file for API Extractor. For more info, please visit: https://api-extractor.com */ { "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", /** * Optionally specifies another JSON config file that this file extends from. This provides a way for * standard settings to be shared across multiple projects. * * If the path starts with "./" or "../", the path is resolved relative to the folder of the file that contains * the "extends" field. Otherwise, the first path segment is interpreted as an NPM package name, and will be * resolved using NodeJS require(). * * SUPPORTED TOKENS: none * DEFAULT VALUE: "" */ "extends": "../../api-extractor-base.json" // "extends": "my-package/include/api-extractor-base.json" } lumino-2021.12.13/packages/application/package.json000066400000000000000000000051621415564225700217740ustar00rootroot00000000000000{ "name": "@lumino/application", "version": "1.27.1", "description": "Lumino Pluggable Application", "homepage": "https://github.com/jupyterlab/lumino", "bugs": { "url": "https://github.com/jupyterlab/lumino/issues" }, "repository": { "type": "git", "url": "https://github.com/jupyterlab/lumino.git" }, "license": "BSD-3-Clause", "author": "S. Chris Colbert ", "contributors": [ "A. Darian ", "Dave Willmer ", "S. Chris Colbert ", "Steven Silvester " ], "main": "dist/index.js", "jsdelivr": "dist/index.min.js", "unpkg": "dist/index.min.js", "module": "dist/index.es6", "types": "types/index.d.ts", "files": [ "dist/*", "src/*", "types/*" ], "scripts": { "api": "api-extractor run --local --verbose", "build": "npm run build:src && rollup -c", "build:src": "tsc --build", "build:test": "tsc --build tests && cd tests && webpack", "clean": "rimraf ./lib && rimraf *.tsbuildinfo && rimraf ./types && rimraf ./dist", "clean:test": "rimraf tests/build", "docs": "typedoc --options tdoptions.json src", "minimize": "terser dist/index.js -c -m --source-map \"content='dist/index.js.map',url='index.min.js.map'\" -o dist/index.min.js", "test": "npm run test:firefox-headless", "test:chrome": "cd tests && karma start --browsers=Chrome", "test:chrome-headless": "cd tests && karma start --browsers=ChromeHeadless", "test:firefox": "cd tests && karma start --browsers=Firefox", "test:firefox-headless": "cd tests && karma start --browsers=FirefoxHeadless", "test:ie": "cd tests && karma start --browsers=IE", "watch": "tsc --build --watch" }, "dependencies": { "@lumino/commands": "^1.19.1", "@lumino/coreutils": "^1.11.1", "@lumino/widgets": "^1.30.1" }, "devDependencies": { "@microsoft/api-extractor": "^7.6.0", "@types/chai": "^3.4.35", "@types/mocha": "^2.2.39", "chai": "^4.3.4", "karma": "^6.3.4", "karma-chrome-launcher": "^3.1.0", "karma-firefox-launcher": "^2.1.1", "karma-ie-launcher": "^1.0.0", "karma-mocha": "^2.0.1", "karma-mocha-reporter": "^2.2.5", "mocha": "^9.0.3", "rimraf": "^3.0.2", "rollup": "^2.56.0", "rollup-plugin-node-resolve": "^5.2.0", "rollup-plugin-postcss": "^4.0.0", "rollup-plugin-sourcemaps": "^0.6.3", "terser": "^5.7.1", "tslib": "^2.3.0", "typedoc": "~0.15.0", "typescript": "~3.6.0", "webpack": "^4.41.3", "webpack-cli": "^3.3.10" }, "publishConfig": { "access": "public" } } lumino-2021.12.13/packages/application/rollup.config.js000066400000000000000000000015231415564225700226220ustar00rootroot00000000000000import nodeResolve from 'rollup-plugin-node-resolve'; import sourcemaps from 'rollup-plugin-sourcemaps'; import postcss from 'rollup-plugin-postcss'; const pkg = require('./package.json'); const globals = id => id.indexOf('@lumino/') === 0 ? id.replace('@lumino/', 'lumino_') : id; export default [ { input: 'lib/index', external: id => pkg.dependencies && !!pkg.dependencies[id], output: [ { file: pkg.main, globals, format: 'umd', sourcemap: true, name: pkg.name }, { file: pkg.module + '.js', format: 'es', sourcemap: true, name: pkg.name } ], plugins: [ nodeResolve({ preferBuiltins: true }), sourcemaps(), postcss({ extensions: ['.css'], minimize: true }) ] } ]; lumino-2021.12.13/packages/application/src/000077500000000000000000000000001415564225700202715ustar00rootroot00000000000000lumino-2021.12.13/packages/application/src/index.ts000066400000000000000000000514531415564225700217600ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { CommandRegistry } from '@lumino/commands'; import { PromiseDelegate, Token } from '@lumino/coreutils'; import { ContextMenu, Menu, Widget } from '@lumino/widgets'; /** * A user-defined application plugin. * * #### Notes * Plugins are the foundation for building an extensible application. * * Plugins consume and provide "services", which are nothing more than * concrete implementations of interfaces and/or abstract types. * * Unlike regular imports and exports, which tie the service consumer * to a particular implementation of the service, plugins decouple the * service producer from the service consumer, allowing an application * to be easily customized by third parties in a type-safe fashion. */ export interface IPlugin { /** * The human readable id of the plugin. * * #### Notes * This must be unique within an application. */ id: string; /** * Whether the plugin should be activated on application start. * * #### Notes * The default is `false`. */ autoStart?: boolean; /** * The types of required services for the plugin, if any. * * #### Notes * These tokens correspond to the services that are required by * the plugin for correct operation. * * When the plugin is activated, a concrete instance of each type * will be passed to the `activate()` function, in the order they * are specified in the `requires` array. */ requires?: Token[]; /** * The types of optional services for the plugin, if any. * * #### Notes * These tokens correspond to the services that can be used by the * plugin if available, but are not necessarily required. * * The optional services will be passed to the `activate()` function * following all required services. If an optional service cannot be * resolved, `null` will be passed in its place. */ optional?: Token[]; /** * The type of service provided by the plugin, if any. * * #### Notes * This token corresponds to the service exported by the plugin. * * When the plugin is activated, the return value of `activate()` * is used as the concrete instance of the type. */ provides?: Token; /** * A function invoked to activate the plugin. * * @param app - The application which owns the plugin. * * @param args - The services specified by the `requires` property. * * @returns The provided service, or a promise to the service. * * #### Notes * This function will be called whenever the plugin is manually * activated, or when another plugin being activated requires * the service it provides. * * This function will not be called unless all of its required * services can be fulfilled. */ activate: (app: T, ...args: any[]) => U | Promise; } /** * A class for creating pluggable applications. * * #### Notes * The `Application` class is useful when creating large, complex * UI applications with the ability to be safely extended by third * party code via plugins. */ export class Application { /** * Construct a new application. * * @param options - The options for creating the application. */ constructor(options: Application.IOptions) { // Create the application command registry. let commands = new CommandRegistry(); // Create the application context menu. let renderer = options.contextMenuRenderer; let contextMenu = new ContextMenu({ commands, renderer }); // Initialize the application state. this.commands = commands; this.contextMenu = contextMenu; this.shell = options.shell; } /** * The application command registry. */ readonly commands: CommandRegistry; /** * The application context menu. */ readonly contextMenu: ContextMenu; /** * The application shell widget. * * #### Notes * The shell widget is the root "container" widget for the entire * application. It will typically expose an API which allows the * application plugins to insert content in a variety of places. */ readonly shell: T; /** * A promise which resolves after the application has started. * * #### Notes * This promise will resolve after the `start()` method is called, * when all the bootstrapping and shell mounting work is complete. */ get started(): Promise { return this._delegate.promise; } /** * Test whether a plugin is registered with the application. * * @param id - The id of the plugin of interest. * * @returns `true` if the plugin is registered, `false` otherwise. */ hasPlugin(id: string): boolean { return id in this._pluginMap; } /** * List the IDs of the plugins registered with the application. * * @returns A new array of the registered plugin IDs. */ listPlugins(): string[] { return Object.keys(this._pluginMap); } /** * Register a plugin with the application. * * @param plugin - The plugin to register. * * #### Notes * An error will be thrown if a plugin with the same id is already * registered, or if the plugin has a circular dependency. * * If the plugin provides a service which has already been provided * by another plugin, the new service will override the old service. */ registerPlugin(plugin: IPlugin): void { // Throw an error if the plugin id is already registered. if (plugin.id in this._pluginMap) { throw new Error(`Plugin '${plugin.id}' is already registered.`); } // Create the normalized plugin data. let data = Private.createPluginData(plugin); // Ensure the plugin does not cause a cyclic dependency. Private.ensureNoCycle(data, this._pluginMap, this._serviceMap); // Add the service token to the service map. if (data.provides) { this._serviceMap.set(data.provides, data.id); } // Add the plugin to the plugin map. this._pluginMap[data.id] = data; } /** * Register multiple plugins with the application. * * @param plugins - The plugins to register. * * #### Notes * This calls `registerPlugin()` for each of the given plugins. */ registerPlugins(plugins: IPlugin[]): void { for (let plugin of plugins) { this.registerPlugin(plugin); } } /** * Activate the plugin with the given id. * * @param id - The ID of the plugin of interest. * * @returns A promise which resolves when the plugin is activated * or rejects with an error if it cannot be activated. */ activatePlugin(id: string): Promise { // Reject the promise if the plugin is not registered. let data = this._pluginMap[id]; if (!data) { return Promise.reject(new Error(`Plugin '${id}' is not registered.`)); } // Resolve immediately if the plugin is already activated. if (data.activated) { return Promise.resolve(undefined); } // Return the pending resolver promise if it exists. if (data.promise) { return data.promise; } // Resolve the required services for the plugin. let required = data.requires.map(t => this.resolveRequiredService(t)); // Resolve the optional services for the plugin. let optional = data.optional.map(t => this.resolveOptionalService(t)); // Create the array of promises to resolve. let promises = required.concat(optional); // Setup the resolver promise for the plugin. data.promise = Promise.all(promises) .then(services => { return data.activate.apply(undefined, [this, ...services]); }) .then(service => { data.service = service; data.activated = true; data.promise = null; }) .catch(error => { data.promise = null; throw error; }); // Return the pending resolver promise. return data.promise; } /** * Resolve a required service of a given type. * * @param token - The token for the service type of interest. * * @returns A promise which resolves to an instance of the requested * service, or rejects with an error if it cannot be resolved. * * #### Notes * Services are singletons. The same instance will be returned each * time a given service token is resolved. * * If the plugin which provides the service has not been activated, * resolving the service will automatically activate the plugin. * * User code will not typically call this method directly. Instead, * the required services for the user's plugins will be resolved * automatically when the plugin is activated. */ resolveRequiredService(token: Token): Promise { // Reject the promise if there is no provider for the type. let id = this._serviceMap.get(token); if (!id) { return Promise.reject(new Error(`No provider for: ${token.name}.`)); } // Resolve immediately if the plugin is already activated. let data = this._pluginMap[id]; if (data.activated) { return Promise.resolve(data.service); } // Otherwise, activate the plugin and wait on the results. return this.activatePlugin(id).then(() => data.service); } /** * Resolve an optional service of a given type. * * @param token - The token for the service type of interest. * * @returns A promise which resolves to an instance of the requested * service, or `null` if it cannot be resolved. * * #### Notes * Services are singletons. The same instance will be returned each * time a given service token is resolved. * * If the plugin which provides the service has not been activated, * resolving the service will automatically activate the plugin. * * User code will not typically call this method directly. Instead, * the optional services for the user's plugins will be resolved * automatically when the plugin is activated. */ resolveOptionalService(token: Token): Promise { // Resolve with `null` if there is no provider for the type. let id = this._serviceMap.get(token); if (!id) { return Promise.resolve(null); } // Resolve immediately if the plugin is already activated. let data = this._pluginMap[id]; if (data.activated) { return Promise.resolve(data.service); } // Otherwise, activate the plugin and wait on the results. return this.activatePlugin(id) .then(() => { return data.service; }) .catch(reason => { console.error(reason); return null; }); } /** * Start the application. * * @param options - The options for starting the application. * * @returns A promise which resolves when all bootstrapping work * is complete and the shell is mounted to the DOM. * * #### Notes * This should be called once by the application creator after all * initial plugins have been registered. * * If a plugin fails to the load, the error will be logged and the * other valid plugins will continue to be loaded. * * Bootstrapping the application consists of the following steps: * 1. Activate the startup plugins * 2. Wait for those plugins to activate * 3. Attach the shell widget to the DOM * 4. Add the application event listeners */ start(options: Application.IStartOptions = {}): Promise { // Return immediately if the application is already started. if (this._started) { return this._delegate.promise; } // Mark the application as started; this._started = true; // Parse the host id for attaching the shell. let hostID = options.hostID || ''; // Collect the ids of the startup plugins. let startups = Private.collectStartupPlugins(this._pluginMap, options); // Generate the activation promises. let promises = startups.map(id => { return this.activatePlugin(id).catch(error => { console.error(`Plugin '${id}' failed to activate.`); console.error(error); }); }); // Wait for the plugins to activate, then finalize startup. Promise.all(promises).then(() => { this.attachShell(hostID); this.addEventListeners(); this._delegate.resolve(undefined); }); // Return the pending delegate promise. return this._delegate.promise; } /** * Handle the DOM events for the application. * * @param event - The DOM event sent to the application. * * #### Notes * This method implements the DOM `EventListener` interface and is * called in response to events registered for the application. It * should not be called directly by user code. */ handleEvent(event: Event): void { switch (event.type) { case 'resize': this.evtResize(event); break; case 'keydown': this.evtKeydown(event as KeyboardEvent); break; case 'contextmenu': this.evtContextMenu(event as MouseEvent); break; } } /** * Attach the application shell to the DOM. * * @param id - The id of the host node for the shell, or `''`. * * #### Notes * If the id is not provided, the document body will be the host. * * A subclass may reimplement this method as needed. */ protected attachShell(id: string): void { Widget.attach( this.shell, (id && document.getElementById(id)) || document.body ); } /** * Add the application event listeners. * * #### Notes * The default implementation of this method adds listeners for * `'keydown'` and `'resize'` events. * * A subclass may reimplement this method as needed. */ protected addEventListeners(): void { document.addEventListener('contextmenu', this); document.addEventListener('keydown', this, true); window.addEventListener('resize', this); } /** * A method invoked on a document `'keydown'` event. * * #### Notes * The default implementation of this method invokes the key down * processing method of the application command registry. * * A subclass may reimplement this method as needed. */ protected evtKeydown(event: KeyboardEvent): void { this.commands.processKeydownEvent(event); } /** * A method invoked on a document `'contextmenu'` event. * * #### Notes * The default implementation of this method opens the application * `contextMenu` at the current mouse position. * * If the application context menu has no matching content *or* if * the shift key is pressed, the default browser context menu will * be opened instead. * * A subclass may reimplement this method as needed. */ protected evtContextMenu(event: MouseEvent): void { if (event.shiftKey) { return; } if (this.contextMenu.open(event)) { event.preventDefault(); event.stopPropagation(); } } /** * A method invoked on a window `'resize'` event. * * #### Notes * The default implementation of this method updates the shell. * * A subclass may reimplement this method as needed. */ protected evtResize(event: Event): void { this.shell.update(); } private _started = false; private _pluginMap = Private.createPluginMap(); private _serviceMap = Private.createServiceMap(); private _delegate = new PromiseDelegate(); } /** * The namespace for the `Application` class statics. */ export namespace Application { /** * An options object for creating an application. */ export interface IOptions { /** * The shell widget to use for the application. * * This should be a newly created and initialized widget. * * The application will attach the widget to the DOM. */ shell: T; /** * A custom renderer for the context menu. */ contextMenuRenderer?: Menu.IRenderer; } /** * An options object for application startup. */ export interface IStartOptions { /** * The ID of the DOM node to host the application shell. * * #### Notes * If this is not provided, the document body will be the host. */ hostID?: string; /** * The plugins to activate on startup. * * #### Notes * These will be *in addition* to any `autoStart` plugins. */ startPlugins?: string[]; /** * The plugins to **not** activate on startup. * * #### Notes * This will override `startPlugins` and any `autoStart` plugins. */ ignorePlugins?: string[]; } } /** * The namespace for the module implementation details. */ namespace Private { /** * An object which holds the full application state for a plugin. */ export interface IPluginData { /** * The human readable id of the plugin. */ readonly id: string; /** * Whether the plugin should be activated on application start. */ readonly autoStart: boolean; /** * The types of required services for the plugin, or `[]`. */ readonly requires: Token[]; /** * The types of optional services for the the plugin, or `[]`. */ readonly optional: Token[]; /** * The type of service provided by the plugin, or `null`. */ readonly provides: Token | null; /** * The function which activates the plugin. */ readonly activate: (app: any, ...args: any[]) => any; /** * Whether the plugin has been activated. */ activated: boolean; /** * The resolved service for the plugin, or `null`. */ service: any | null; /** * The pending resolver promise, or `null`. */ promise: Promise | null; } /** * A type alias for a mapping of plugin id to plugin data. */ export type PluginMap = { [id: string]: IPluginData }; /** * A type alias for a mapping of service token to plugin id. */ export type ServiceMap = Map, string>; /** * Create a new plugin map. */ export function createPluginMap(): PluginMap { return Object.create(null); } /** * Create a new service map. */ export function createServiceMap(): ServiceMap { return new Map, string>(); } /** * Create a normalized plugin data object for the given plugin. */ export function createPluginData(plugin: IPlugin): IPluginData { return { id: plugin.id, service: null, promise: null, activated: false, activate: plugin.activate, provides: plugin.provides || null, autoStart: plugin.autoStart || false, requires: plugin.requires ? plugin.requires.slice() : [], optional: plugin.optional ? plugin.optional.slice() : [] }; } /** * Ensure no cycle is present in the plugin resolution graph. * * If a cycle is detected, an error will be thrown. */ export function ensureNoCycle( data: IPluginData, pluginMap: PluginMap, serviceMap: ServiceMap ): void { let dependencies = data.requires.concat(data.optional); // Bail early if there cannot be a cycle. if (!data.provides || dependencies.length === 0) { return; } // Setup a stack to trace service resolution. let trace = [data.id]; // Throw an exception if a cycle is present. if (dependencies.some(visit)) { throw new Error(`Cycle detected: ${trace.join(' -> ')}.`); } function visit(token: Token): boolean { if (token === data.provides) { return true; } let id = serviceMap.get(token); if (!id) { return false; } let other = pluginMap[id]; let otherDependencies = other.requires.concat(other.optional); if (otherDependencies.length === 0) { return false; } trace.push(id); if (otherDependencies.some(visit)) { return true; } trace.pop(); return false; } } /** * Collect the IDs of the plugins to activate on startup. */ export function collectStartupPlugins( pluginMap: PluginMap, options: Application.IStartOptions ): string[] { // Create a map to hold the plugin IDs. let resultMap: { [id: string]: boolean } = Object.create(null); // Collect the auto-start plugins. for (let id in pluginMap) { if (pluginMap[id].autoStart) { resultMap[id] = true; } } // Add the startup plugins. if (options.startPlugins) { for (let id of options.startPlugins) { resultMap[id] = true; } } // Remove the ignored plugins. if (options.ignorePlugins) { for (let id of options.ignorePlugins) { delete resultMap[id]; } } // Return the final startup plugins. return Object.keys(resultMap); } } lumino-2021.12.13/packages/application/tdoptions.json000066400000000000000000000005111415564225700224150ustar00rootroot00000000000000{ "excludeNotExported": true, "mode": "file", "target": "es5", "module": "es5", "lib": [ "lib.es2015.d.ts", "lib.es2015.collection.d.ts", "lib.es2015.promise.d.ts", "lib.dom.d.ts" ], "out": "../../docs/source/api/application", "baseUrl": ".", "paths": { "@lumino/*": ["../packages/*"] } } lumino-2021.12.13/packages/application/tests/000077500000000000000000000000001415564225700206445ustar00rootroot00000000000000lumino-2021.12.13/packages/application/tests/karma.conf.js000066400000000000000000000005051415564225700232210ustar00rootroot00000000000000module.exports = function (config) { config.set({ basePath: '.', frameworks: ['mocha'], reporters: ['mocha'], files: ['build/bundle.test.js'], port: 9876, colors: true, singleRun: true, browserNoActivityTimeout: 30000, failOnEmptyTestSuite: false, logLevel: config.LOG_INFO }); }; lumino-2021.12.13/packages/application/tests/src/000077500000000000000000000000001415564225700214335ustar00rootroot00000000000000lumino-2021.12.13/packages/application/tests/src/index.spec.ts000066400000000000000000000011231415564225700240400ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { expect } from 'chai'; describe('@lumino/application', () => { it('should pass', () => { expect(true).to.equal(true); }); }); lumino-2021.12.13/packages/application/tests/tsconfig.json000066400000000000000000000010121415564225700233450ustar00rootroot00000000000000{ "compilerOptions": { "declaration": false, "noImplicitAny": true, "noEmitOnError": true, "noUnusedLocals": true, "strictNullChecks": true, "module": "commonjs", "moduleResolution": "node", "target": "ES5", "outDir": "build", "lib": ["ES5"], "types": ["chai", "mocha"], "rootDir": "src" }, "include": ["src/*"], "references": [ { "path": "../../commands" }, { "path": "../../coreutils" }, { "path": "../../widgets" } ] } lumino-2021.12.13/packages/application/tests/webpack.config.js000066400000000000000000000003061415564225700240610ustar00rootroot00000000000000var path = require('path'); module.exports = { entry: './build/index.spec.js', mode: 'development', output: { filename: './build/bundle.test.js', path: path.resolve(__dirname) } }; lumino-2021.12.13/packages/application/tsconfig.json000066400000000000000000000013251415564225700222120ustar00rootroot00000000000000{ "compilerOptions": { "composite": true, "sourceMap": true, "declaration": true, "declarationDir": "./types", "declarationMap": true, "noImplicitAny": true, "noEmitOnError": true, "noUnusedLocals": true, "strictNullChecks": true, "module": "ES6", "moduleResolution": "node", "target": "ES5", "outDir": "lib", "lib": [ "ES5", "ES2015.Collection", "ES2015.Promise", "ES2015.Iterable", "DOM" ], "importHelpers": true, "types": [], "rootDir": "src" }, "include": ["src/*"], "references": [ { "path": "../commands" }, { "path": "../coreutils" }, { "path": "../widgets" } ] } lumino-2021.12.13/packages/collections/000077500000000000000000000000001415564225700175155ustar00rootroot00000000000000lumino-2021.12.13/packages/collections/api-extractor.json000066400000000000000000000014611415564225700231740ustar00rootroot00000000000000/** * Config file for API Extractor. For more info, please visit: https://api-extractor.com */ { "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", /** * Optionally specifies another JSON config file that this file extends from. This provides a way for * standard settings to be shared across multiple projects. * * If the path starts with "./" or "../", the path is resolved relative to the folder of the file that contains * the "extends" field. Otherwise, the first path segment is interpreted as an NPM package name, and will be * resolved using NodeJS require(). * * SUPPORTED TOKENS: none * DEFAULT VALUE: "" */ "extends": "../../api-extractor-base.json" // "extends": "my-package/include/api-extractor-base.json" } lumino-2021.12.13/packages/collections/package.json000066400000000000000000000050511415564225700220040ustar00rootroot00000000000000{ "name": "@lumino/collections", "version": "1.9.1", "description": "Lumino Generic Collections", "homepage": "https://github.com/jupyterlab/lumino", "bugs": { "url": "https://github.com/jupyterlab/lumino/issues" }, "repository": { "type": "git", "url": "https://github.com/jupyterlab/lumino.git" }, "license": "BSD-3-Clause", "author": "S. Chris Colbert ", "contributors": [ "A. Darian ", "Dave Willmer ", "S. Chris Colbert ", "Steven Silvester " ], "main": "dist/index.js", "jsdelivr": "dist/index.min.js", "unpkg": "dist/index.min.js", "module": "dist/index.es6", "types": "types/index.d.ts", "files": [ "dist/*", "src/*", "types/*" ], "scripts": { "api": "api-extractor run --local --verbose", "build": "npm run build:src && rollup -c", "build:src": "tsc --build", "build:test": "tsc --build tests && cd tests && webpack", "clean": "rimraf ./lib && rimraf *.tsbuildinfo && rimraf ./types && rimraf ./dist", "clean:test": "rimraf tests/build", "docs": "typedoc --options tdoptions.json src", "minimize": "terser dist/index.js -c -m --source-map \"content='dist/index.js.map',url='index.min.js.map'\" -o dist/index.min.js", "test": "npm run test:firefox-headless", "test:chrome": "cd tests && karma start --browsers=Chrome", "test:chrome-headless": "cd tests && karma start --browsers=ChromeHeadless", "test:firefox": "cd tests && karma start --browsers=Firefox", "test:firefox-headless": "cd tests && karma start --browsers=FirefoxHeadless", "test:ie": "cd tests && karma start --browsers=IE", "watch": "tsc --build --watch" }, "dependencies": { "@lumino/algorithm": "^1.9.1" }, "devDependencies": { "@microsoft/api-extractor": "^7.6.0", "@types/chai": "^3.4.35", "@types/mocha": "^2.2.39", "chai": "^4.3.4", "karma": "^6.3.4", "karma-chrome-launcher": "^3.1.0", "karma-firefox-launcher": "^2.1.1", "karma-ie-launcher": "^1.0.0", "karma-mocha": "^2.0.1", "karma-mocha-reporter": "^2.2.5", "mocha": "^9.0.3", "rimraf": "^3.0.2", "rollup": "^2.56.0", "rollup-plugin-node-resolve": "^5.2.0", "rollup-plugin-postcss": "^4.0.0", "rollup-plugin-sourcemaps": "^0.6.3", "terser": "^5.7.1", "tslib": "^2.3.0", "typedoc": "~0.15.0", "typescript": "~3.6.0", "webpack": "^4.41.3", "webpack-cli": "^3.3.10" }, "publishConfig": { "access": "public" } } lumino-2021.12.13/packages/collections/rollup.config.js000066400000000000000000000015231415564225700226350ustar00rootroot00000000000000import nodeResolve from 'rollup-plugin-node-resolve'; import sourcemaps from 'rollup-plugin-sourcemaps'; import postcss from 'rollup-plugin-postcss'; const pkg = require('./package.json'); const globals = id => id.indexOf('@lumino/') === 0 ? id.replace('@lumino/', 'lumino_') : id; export default [ { input: 'lib/index', external: id => pkg.dependencies && !!pkg.dependencies[id], output: [ { file: pkg.main, globals, format: 'umd', sourcemap: true, name: pkg.name }, { file: pkg.module + '.js', format: 'es', sourcemap: true, name: pkg.name } ], plugins: [ nodeResolve({ preferBuiltins: true }), sourcemaps(), postcss({ extensions: ['.css'], minimize: true }) ] } ]; lumino-2021.12.13/packages/collections/src/000077500000000000000000000000001415564225700203045ustar00rootroot00000000000000lumino-2021.12.13/packages/collections/src/bplustree.ts000066400000000000000000001127251415564225700226710ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2018, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { ArrayExt, each, empty, IIterable, IIterator, IRetroable, IterableOrArrayLike } from '@lumino/algorithm'; /** * A generic B+ tree. * * #### Notes * Most operations have `O(log32 n)` or better complexity. */ export class BPlusTree implements IIterable, IRetroable { /** * Construct a new B+ tree. * * @param cmp - The item comparison function for the tree. */ constructor(cmp: (a: T, b: T) => number) { this.cmp = cmp; } /** * The item comparison function for the tree. * * #### Complexity * `O(1)` */ readonly cmp: (a: T, b: T) => number; /** * Whether the tree is empty. * * #### Complexity * `O(1)` */ get isEmpty(): boolean { return this._root.size === 0; } /** * The size of the tree. * * #### Complexity * `O(1)` */ get size(): number { return this._root.size; } /** * The first item in the tree. * * This is `undefined` if the tree is empty. * * #### Complexity * `O(log32 n)` */ get first(): T | undefined { let node = Private.firstLeaf(this._root); return node.size > 0 ? node.items[0] : undefined; } /** * The last item in the tree. * * This is `undefined` if the tree is empty. * * #### Complexity * `O(log32 n)` */ get last(): T | undefined { let node = Private.lastLeaf(this._root); return node.size > 0 ? node.items[node.size - 1] : undefined; } /** * Create an iterator over the items in the tree. * * @returns A new iterator starting with the first item. * * #### Complexity * `O(log32 n)` */ iter(): IIterator { return Private.iterItems(this._root); } /** * Create a reverse iterator over the items in the tree. * * @returns A new iterator starting with the last item. * * #### Complexity * `O(log32 n)` */ retro(): IIterator { return Private.retroItems(this._root); } /** * Create an iterator for a slice of items in the tree. * * @param start - The index of the first item, inclusive. This * should be `< stop`. Negative values are taken as an offset * from the end of the tree. The default is `0`. * * @param stop - The index of the last item, exclusive. This * should be `> start`. Negative values are taken as an offset * from the end of the tree. The default is `size`. * * @returns A new iterator starting with the specified item. * * #### Complexity * `O(log32 n)` */ slice(start?: number, stop?: number): IIterator { return Private.sliceItems(this._root, start, stop); } /** * Create a reverse iterator for a slice of items in the tree. * * @param start - The index of the first item, inclusive. This * should be `> stop`. Negative values are taken as an offset * from the end of the tree. The default is `size - 1`. * * @param stop - The index of the last item, exclusive. This * should be `< start`. Negative values are taken as an offset * from the end of the tree. The default is `-size - 1`. * * @returns A new reverse iterator starting with the specified item. * * #### Complexity * `O(log32 n)` */ retroSlice(start?: number, stop?: number): IIterator { return Private.retroSliceItems(this._root, start, stop); } /** * Get the item at a particular index. * * @param index - The index of the item of interest. Negative * values are taken as an offset from the end of the tree. * * @returns The item at the specified index, or `undefined` if * the index is out of range. * * #### Complexity * `O(log32 n)` */ at(index: number): T | undefined { return Private.itemAt(this._root, index); } /** * Test whether the tree has an item which matches a key. * * @param key - The key of interest. * * @param cmp - A function which compares an item against the key. * * @returns `true` if the tree has an item which matches the given * key, `false` otherwise. * * #### Complexity * `O(log32 n)` */ has(key: U, cmp: (item: T, key: U) => number): boolean { return Private.hasItem(this._root, key, cmp); } /** * Get the index of an item which matches a key. * * @param key - The key of interest. * * @param cmp - A function which compares an item against the key. * * @returns The index of the item which matches the given key. A * negative value means that a matching item does not exist in * the tree, but if one did it would reside at `-index - 1`. * * #### Complexity * `O(log32 n)` */ indexOf(key: U, cmp: (item: T, key: U) => number): number { return Private.indexOf(this._root, key, cmp); } /** * Get the item which matches a key. * * @param item - The key of interest. * * @param cmp - A function which compares an item against the key. * * @returns The item which matches the given key, or `undefined` if * the tree does not have a matching item. * * #### Complexity * `O(log32 n)` */ get(key: U, cmp: (item: T, key: U) => number): T | undefined { return Private.getItem(this._root, key, cmp); } /** * Assign new items to the tree, replacing all current items. * * @param items - The items to assign to the tree. * * #### Complexity * `O(n log32 n)` */ assign(items: IterableOrArrayLike): void { this.clear(); this.update(items); } /** * Insert an item into the tree. * * @param item - The item of interest. * * @returns If the given item matches an existing item in the tree, * the given item will replace it, and the existing item will be * returned. Otherwise, this method returns `undefined`. * * #### Complexity * `O(log32 n)` */ insert(item: T): T | undefined { let existing = Private.insertItem(this._root, item, this.cmp); this._root = Private.maybeSplitRoot(this._root); return existing; } /** * Update the tree with multiple items. * * @param items - The items to insert into the tree. * * #### Complexity * `O(k log32 n)` */ update(items: IterableOrArrayLike): void { each(items, item => { this.insert(item); }); } /** * Delete an item which matches a particular key. * * @param key - The key of interest. * * @param cmp - A function which compares an item against the key. * * @returns The item removed from the tree, or `undefined` if no * item matched the given key. * * #### Complexity * `O(log32 n)` */ delete(key: U, cmp: (item: T, key: U) => number): T | undefined { let item = Private.deleteItem(this._root, key, cmp); this._root = Private.maybeExtractRoot(this._root); return item; } /** * Remove an item at a particular index. * * @param index - The index of the item to remove. Negative * values are taken as an offset from the end of the tree. * * @returns The item removed from the tree, or `undefined` if * the given index is out of range. * * #### Complexity * `O(log32 n)` */ remove(index: number): T | undefined { let item = Private.removeItem(this._root, index); this._root = Private.maybeExtractRoot(this._root); return item; } /** * Clear the contents of the tree. * * #### Complexity * `O(n)` */ clear(): void { Private.clear(this._root); this._root = new Private.LeafNode(); } private _root: Private.Node = new Private.LeafNode(); } /** * The namespace for the `BPlusTree` class statics. */ export namespace BPlusTree { /** * Create a new B+ tree populated with the given items. * * @param items - The items to add to the tree. * * @param cmp - The item comparison function for the tree. * * @returns A new B+ tree populated with the given items. * * #### Complexity * `O(n log32 n)` */ export function from( items: IterableOrArrayLike, cmp: (a: T, b: T) => number ): BPlusTree { let tree = new BPlusTree(cmp); tree.assign(items); return tree; } } /** * The namespace for the module implementation details. */ namespace Private { /** * A const enum of the B+ tree node types. */ export const enum NodeType { Branch, Leaf } /** * A branch node in a B+ tree. */ export class BranchNode { /** * The left-most item of each child subtree. */ readonly items: T[] = []; /** * The cumulative sizes of each child subtree. */ readonly sizes: number[] = []; /** * The child nodes of this branch node. */ readonly children: Node[] = []; /** * The discriminated type of the node. */ get type(): NodeType.Branch { return NodeType.Branch; } /** * The total number of items in the subtree. */ get size(): number { return this.sizes[this.sizes.length - 1]; } /** * The tree width of the node. */ get width(): number { return this.children.length; } } /** * A leaf node in a B+ tree. */ export class LeafNode { /** * The next sibling leaf node of this leaf node. */ next: LeafNode | null = null; /** * The previous sibling leaf node of this leaf node. */ prev: LeafNode | null = null; /** * The items of the leaf. */ readonly items: T[] = []; /** * The discriminated type of the node. */ get type(): NodeType.Leaf { return NodeType.Leaf; } /** * The total number of items in the leaf. */ get size(): number { return this.items.length; } /** * The tree width of the node. */ get width(): number { return this.items.length; } } /** * A type alias for the B+ tree nodes. */ export type Node = BranchNode | LeafNode; /** * Get the first leaf node in the tree. * * @param node - The root node of interest. * * @returns The first leaf node in the tree. * * #### Complexity * `O(log32 n)` */ export function firstLeaf(node: Node): LeafNode { while (node.type === NodeType.Branch) { node = node.children[0]; } return node; } /** * Get the last leaf node in the tree. * * @param node - The root node of interest. * * @returns The last leaf node in the tree. * * #### Complexity * `O(log32 n)` */ export function lastLeaf(node: Node): LeafNode { while (node.type === NodeType.Branch) { node = node.children[node.children.length - 1]; } return node; } /** * Create a forward iterator for the items in the tree. * * @param node - The root node of interest. * * @returns A new forward iterator starting with the first item. * * #### Complexity * `O(log32 n)` */ export function iterItems(node: Node): IIterator { let leaf = firstLeaf(node); return new ForwardIterator(leaf, 0, -1); } /** * Create a reverse iterator for the items in the tree. * * @param node - The root node of interest. * * @returns A new reverse iterator starting with the last item. * * #### Complexity * `O(log32 n)` */ export function retroItems(node: Node): IIterator { let leaf = lastLeaf(node); return new RetroIterator(leaf, leaf.size - 1, -1); } /** * Create an iterator for a slice of items in the tree. * * @param node - The root node of interest. * * @param start - The index of the first item, inclusive. This * should be `< stop`. Negative values are taken as an offset * from the end of the tree. The default is `0`. * * @param stop - The index of the last item, exclusive. This * should be `> start`. Negative values are taken as an offset * from the end of the tree. The default is `size`. * * @returns A new iterator starting with the specified item. * * #### Complexity * `O(log32 n)` */ export function sliceItems( node: Node, start?: number, stop?: number ): IIterator { // Normalize the start index. if (start === undefined) { start = 0; } else if (start < 0) { start = Math.max(0, start + node.size); } else { start = Math.min(start, node.size); } // Normalize the stop index. if (stop === undefined) { stop = node.size; } else if (stop < 0) { stop = Math.max(0, stop + node.size); } else { stop = Math.min(stop, node.size); } // Compute effective count. let count = Math.max(0, stop - start); // Bail early if there is nothing to iterate. if (count === 0) { return empty(); } // Find the starting leaf node and local index. while (node.type === NodeType.Branch) { let i = findPivotIndexByIndex(node.sizes, start); if (i > 0) start -= node.sizes[i - 1]; node = node.children[i]; } // Return the forward iterator for the range. return new ForwardIterator(node, start, count); } /** * Create a reverse iterator for a slice of items in the tree. * * @param node - The root node of interest. * * @param start - The index of the first item, inclusive. This * should be `> stop`. Negative values are taken as an offset * from the end of the tree. The default is `size - 1`. * * @param stop - The index of the last item, exclusive. This * should be `< start`. Negative values are taken as an offset * from the end of the tree. The default is `-size - 1`. * * @returns A new reverse iterator starting with the specified item. * * #### Complexity * `O(log32 n)` */ export function retroSliceItems( node: Node, start?: number, stop?: number ): IIterator { // Normalize the start index. if (start === undefined) { start = node.size - 1; } else if (start < 0) { start = Math.max(-1, start + node.size); } else { start = Math.min(start, node.size - 1); } // Normalize the stop index. if (stop === undefined) { stop = -1; } else if (stop < 0) { stop = Math.max(-1, stop + node.size); } else { stop = Math.min(stop, node.size - 1); } // Compute the effective count. let count = Math.max(0, start - stop); // Bail early if there is nothing to iterate. if (count === 0) { return empty(); } // Find the starting leaf node and local index. while (node.type === NodeType.Branch) { let i = findPivotIndexByIndex(node.sizes, start); if (i > 0) start -= node.sizes[i - 1]; node = node.children[i]; } // Return the retro iterator for the range. return new RetroIterator(node, start, count); } /** * Get the item at the specified index. * * @param node - The root node of interest. * * @param index - The index of the item of interest. Negative * values are taken as an offset from the end of the tree. * * @returns The item at the specified index, or `undefined` if * the index is out of range. * * #### Complexity * `O(log32 n)` */ export function itemAt(node: Node, index: number): T | undefined { // Wrap negative indices. if (index < 0) { index += node.size; } // Bail early if the index is out of range. if (index < 0 || index >= node.size) { return undefined; } // Find the containing leaf node and local index. while (node.type === NodeType.Branch) { let i = findPivotIndexByIndex(node.sizes, index); if (i > 0) index -= node.sizes[i - 1]; node = node.children[i]; } // Return the item at the specified index. return node.items[index]; } /** * Test whether the tree contains an item which matches a key. * * @param node - The root node of interest. * * @param key - The key of interest. * * @param cmp - The key comparison function. * * @returns Whether the tree contains a matching item. * * #### Complexity * `O(log32 n)` */ export function hasItem( node: Node, key: U, cmp: (item: T, key: U) => number ): boolean { // Find the containing leaf node. while (node.type === NodeType.Branch) { let i = findPivotIndexByKey(node.items, key, cmp); node = node.children[i]; } // Find the key index. let i = findKeyIndex(node.items, key, cmp); // Return whether or not the node contains a matching item. return i >= 0; } /** * Get the index of the item which matches a key. * * @param node - The node of interest. * * @param key - The key of interest. * * @param cmp - The key comparison function. * * @returns The index of the item which matches the given key. A * negative value means that a matching item does not exist in * the tree, but if one did it would reside at `-index - 1`. * * #### Complexity * `O(log32 n)` */ export function indexOf( node: Node, key: U, cmp: (item: T, key: U) => number ): number { // Set up the global index. let index = 0; // Find the containing leaf node and global index. while (node.type === NodeType.Branch) { let i = findPivotIndexByKey(node.items, key, cmp); if (i > 0) index += node.sizes[i - 1]; node = node.children[i]; } // Find the key index. let i = findKeyIndex(node.items, key, cmp); // Return the final computed index. return i >= 0 ? index + i : -index + i; } /** * Get the item for a particular key. * * @param node - The node of interest. * * @param key - The key of interest. * * @param cmp - The key comparison function. * * @returns The item for the specified key, or `undefined` if * the tree does not have a matching item for the key. * * #### Complexity * `O(log32 n)` */ export function getItem( node: Node, key: U, cmp: (item: T, key: U) => number ): T | undefined { // Find the containing leaf node. while (node.type === NodeType.Branch) { let i = findPivotIndexByKey(node.items, key, cmp); node = node.children[i]; } // Find the key index. let i = findKeyIndex(node.items, key, cmp); // Return the item for the given key. return i >= 0 ? node.items[i] : undefined; } /** * Insert an item into the tree. * * @param node - The root node of interest. * * @param item - The item of interest. * * @param cmp - The item comparison function. * * @returns If the given item matches an existing item in the tree, * the given item will replace it, and the existing item will be * returned. Otherwise, this function returns `undefined`. * * #### Complexity * `O(log32 n)` * * #### Notes * The root may be overfull after calling this function. */ export function insertItem( node: Node, item: T, cmp: (a: T, b: T) => number ): T | undefined { // Handle leaf nodes first. if (node.type === NodeType.Leaf) { // Find the index for the given item. let i = findKeyIndex(node.items, item, cmp); // Fetch the existing item and insert the new item. let existing: T | undefined; if (i >= 0) { existing = node.items[i]; node.items[i] = item; } else { existing = undefined; ArrayExt.insert(node.items, -i - 1, item); } // Return the existing item. return existing; } // Find the pivot index for the insert. let i = findPivotIndexByKey(node.items, item, cmp); // Fetch the pivot child. let child = node.children[i]; // Fetch the current size of the child. let prevSize = child.size; // Recursively insert the item into the child. let existing = insertItem(child, item, cmp); // Fetch the updated size of the child. let currSize = child.size; // Update the item state of the branch. node.items[i] = child.items[0]; // Bail early if the child size did not change. if (prevSize === currSize) { return existing; } // Split the child if it's overfull. if (child.width > MAX_NODE_WIDTH) { let next = splitNode(child); ArrayExt.insert(node.children, i + 1, next); ArrayExt.insert(node.items, i + 1, next.items[0]); } // Update the dirty sizes of the branch. updateSizes(node, i); // Return the existing item. return existing; } /** * Delete an item in the tree. * * @param node - The node of interest. * * @param key - The key of interest. * * @param cmp - The key comparison function. * * @returns The deleted item or `undefined`. * * #### Complexity * `O(log32 n)` * * #### Notes * The root may be underfull after calling this function. */ export function deleteItem( node: Node, key: U, cmp: (item: T, key: U) => number ): T | undefined { // Handle leaf nodes first. if (node.type === NodeType.Leaf) { // Find the index for the given key. let i = findKeyIndex(node.items, key, cmp); // Bail early if the item does not exist. if (i < 0) { return undefined; } // Remove the item at the computed index. return ArrayExt.removeAt(node.items, i); } // Find the pivot index for the delete. let i = findPivotIndexByKey(node.items, key, cmp); // Fetch the pivot child. let child = node.children[i]; // Fetch the current size of the child. let prevSize = child.size; // Recursively remove the item from the child. let item = deleteItem(child, key, cmp); // Fetch the updated size of the child. let currSize = child.size; // Bail early if the child size did not change. if (prevSize === currSize) { return item; } // Update the item state of the branch. node.items[i] = child.items[0]; // Join the child if it's underfull. if (child.width < MIN_NODE_WIDTH) { i = joinChild(node, i); } // Update the dirty sizes of the branch. updateSizes(node, i); // Return the deleted item. return item; } /** * Remove an item from the tree. * * @param node - The node of interest. * * @param index - The index of interest. * * @returns The removed item or `undefined`. * * #### Complexity * `O(log32 n)` * * #### Notes * The root may be underfull after calling this function. */ export function removeItem(node: Node, index: number): T | undefined { // Wrap negative indices. if (index < 0) { index += node.size; } // Bail early if the index is out of range. if (index < 0 || index >= node.size) { return undefined; } // Handle leaf nodes first. if (node.type === NodeType.Leaf) { return ArrayExt.removeAt(node.items, index); } // Find the pivot index for the remove. let i = findPivotIndexByIndex(node.sizes, index); if (i > 0) index -= node.sizes[i]; // Fetch the pivot child. let child = node.children[i]; // Recursively remove the item from the child. let item = removeItem(child, index); // Update the item state of the branch. node.items[i] = child.items[0]; // Join the child if it's underfull. if (child.width < MIN_NODE_WIDTH) { i = joinChild(node, i); } // Update the dirty sizes of the branch. updateSizes(node, i); // Return the removed item. return item; } /** * Recursively clear the contents of a node. * * @param node - The node of interest. * * #### Complexity * `O(n)` */ export function clear(node: Node): void { if (node.type === NodeType.Branch) { each(node.children, clear); node.children.length = 0; node.sizes.length = 0; node.items.length = 0; } else { node.items.length = 0; node.next = null; node.prev = null; } } /** * Split a root node and create a new root, if needed. * * @param node - The root node of interest. * * @returns The new root node. */ export function maybeSplitRoot(node: Node): Node { // Bail early if the current root is not overfull. if (node.width <= MAX_NODE_WIDTH) { return node; } // Create a new root branch node. let root = new BranchNode(); // Split the node to the right and create a new sibling. let next = splitNode(node); // Add the sizes to the root. root.sizes[0] = node.size; root.sizes[1] = node.size + next.size; // Add the children to the root. root.children[0] = node; root.children[1] = next; // Add the items to the root. root.items[0] = node.items[0]; root.items[1] = next.items[0]; // Return the new root node. return root; } /** * Extract a single node child as a new root, if needed. * * @param node - The root node of interest. * * @returns The new root node. */ export function maybeExtractRoot(node: Node): Node { // Bail early if the node it already a leaf. if (node.type === NodeType.Leaf) { return node; } // Bail early if the branch has more than one child. if (node.children.length > 1) { return node; } // Extract the sole remaining child as the new root. let root = node.children.pop()!; // Clear the rest of the node state. clear(node); // Return the new root. return root; } /** * The maximum width for a node in the tree. */ const MAX_NODE_WIDTH = 32; /** * The minimum width for a node in the tree. */ const MIN_NODE_WIDTH = MAX_NODE_WIDTH >> 1; /** * A forward iterator for a B+ tree. */ class ForwardIterator implements IIterator { /** * Construct a new forward iterator. * * @param node - The first leaf node in the chain. * * @param index - The local index of the first item. * * @param count - The number of items to iterate. A value `< 0` * will iterate all available items. */ constructor(node: LeafNode | null, index: number, count: number) { this._node = node; this._index = index; this._count = count; } /** * Get an iterator over the object's values. * * @returns An iterator which yields the object's values. */ iter(): IIterator { return this; } /** * Create an independent clone of the iterator. * * @returns A new independent clone of the iterator. */ clone(): IIterator { return new ForwardIterator(this._node, this._index, this._count); } /** * Get the next value from the iterator. * * @returns The next value from the iterator, or `undefined`. */ next(): T | undefined { if (this._node === null || this._count === 0) { return undefined; } if (this._index >= this._node.size) { this._node = this._node.next; this._index = 0; return this.next(); } if (this._count > 0) { this._count--; } return this._node.items[this._index++]; } private _index: number; private _count: number; private _node: LeafNode | null; } /** * A reverse iterator for a B+ tree. */ class RetroIterator implements IIterator { /** * Construct a new retro iterator. * * @param node - The last leaf node in the chain. * * @param index - The local index of the last item. * * @param count - The number of items to iterate. A value `< 0` * will iterate all available items. */ constructor(node: LeafNode | null, index: number, count: number) { this._node = node; this._index = index; this._count = count; } /** * Get an iterator over the object's values. * * @returns An iterator which yields the object's values. */ iter(): IIterator { return this; } /** * Create an independent clone of the iterator. * * @returns A new independent clone of the iterator. */ clone(): IIterator { return new RetroIterator(this._node, this._index, this._count); } /** * Get the next value from the iterator. * * @returns The next value from the iterator, or `undefined`. */ next(): T | undefined { if (this._node === null || this._count === 0) { return undefined; } if (this._index >= this._node.size) { this._index = this._node.size - 1; } if (this._index < 0) { this._node = this._node.prev; this._index = this._node ? this._node.size - 1 : -1; return this.next(); } if (this._count > 0) { this._count--; } return this._node.items[this._index--]; } private _index: number; private _count: number; private _node: LeafNode | null; } /** * Find the pivot index for a particular local index. */ function findPivotIndexByIndex(sizes: number[], index: number): number { let n = sizes.length; for (let i = 0; i < n; ++i) { if (sizes[i] > index) { return i; } } return n - 1; } /** * Find the pivot index for a particular key. */ function findPivotIndexByKey( items: T[], key: U, cmp: (item: T, key: U) => number ): number { let n = items.length; for (let i = 1; i < n; ++i) { if (cmp(items[i], key) > 0) { return i - 1; } } return n - 1; } /** * Find the key index for a particular key. */ function findKeyIndex( items: T[], key: U, cmp: (item: T, key: U) => number ): number { let n = items.length; for (let i = 0; i < n; ++i) { let c = cmp(items[i], key); if (c === 0) { return i; } if (c > 0) { return -i - 1; } } return -n - 1; } /** * Update the sizes of a branch node starting at the given index. */ function updateSizes(node: BranchNode, i: number): void { let { sizes, children } = node; let last = i > 0 ? sizes[i - 1] : 0; for (let n = children.length; i < n; ++i) { last = sizes[i] = last + children[i].size; } sizes.length = children.length; } /** * Split a node and return its new next sibling. * * @param node - The node of interest. * * @returns The new next sibling node. */ function splitNode(node: Node): Node { // Handle leaf nodes first. if (node.type === NodeType.Leaf) { // Create the new sibling leaf node. let next = new LeafNode(); // Move the items to the new sibling. let v1 = node.items; let v2 = next.items; for (let i = MIN_NODE_WIDTH, n = v1.length; i < n; ++i) { v2.push(v1[i]); } v1.length = MIN_NODE_WIDTH; // Patch up the sibling links. if (node.next) node.next.prev = next; next.next = node.next; next.prev = node; node.next = next; // Return the new next sibling. return next; } // Create the new sibling branch node. let next = new BranchNode(); // Move the children to the new sibling. let c1 = node.children; let c2 = next.children; for (let i = MIN_NODE_WIDTH, n = c1.length; i < n; ++i) { c2.push(c1[i]); } c1.length = MIN_NODE_WIDTH; // Move the items to the new sibling. let v1 = node.items; let v2 = next.items; for (let i = MIN_NODE_WIDTH, n = v1.length; i < n; ++i) { v2.push(v1[i]); } v1.length = MIN_NODE_WIDTH; // Update the dirty sizes of the nodes. updateSizes(node, MIN_NODE_WIDTH); updateSizes(next, 0); // Return the new next sibling. return next; } /** * Join a child node of a branch with one of its siblings. * * @param node - The branch node of interest. * * @param i - The index of the child node of interest. * * @returns The first modified index. * * #### Notes * This may cause the branch to become underfull. */ function joinChild(node: BranchNode, i: number): number { // Fetch the child to be joined. let child = node.children[i]; // Fetch the relevant sibling. let sibling = i === 0 ? node.children[i + 1] : node.children[i - 1]; // Compute the flags which control the join behavior. let hasNext = i === 0; let isLeaf = child.type === NodeType.Leaf; let hasExtra = sibling.width > MIN_NODE_WIDTH; // Join case #1: steal from next sibling leaf if (isLeaf && hasExtra && hasNext) { // Cast the children as leaves. let c = child as LeafNode; let s = sibling as LeafNode; // Steal an item. c.items.push(s.items.shift()!); // Update the branch items. node.items[i + 1] = s.items[0]; // Return the first modified index. return i; } // Join case #2: steal from previous sibling leaf if (isLeaf && hasExtra && !hasNext) { // Cast the children as leaves. let c = child as LeafNode; let s = sibling as LeafNode; // Steal an item. c.items.unshift(s.items.pop()!); // Update the branch items. node.items[i] = c.items[0]; // Return the first modified index. return i - 1; } // Join case #3: merge with next sibling leaf if (isLeaf && !hasExtra && hasNext) { // Cast the children as leaves. let c = child as LeafNode; let s = sibling as LeafNode; // Merge items. s.items.unshift(...c.items); // Remove the old branch child. ArrayExt.removeAt(node.children, i); // Remove the stale branch item. ArrayExt.removeAt(node.items, i + 1); // Patch up the sibling links. if (c.prev) c.prev.next = s; s.prev = c.prev; // Clear the original child. clear(c); // Return the first modified index. return i; } // Join case #4: merge with previous sibling leaf if (isLeaf && !hasExtra && !hasNext) { // Cast the children as leaves. let c = child as LeafNode; let s = sibling as LeafNode; // Merge items. s.items.push(...c.items); // Remove the old branch child. ArrayExt.removeAt(node.children, i); // Remove the stale branch item. ArrayExt.removeAt(node.items, i); // Patch up the sibling links. if (c.next) c.next.prev = s; s.next = c.next; // Clear the original child. clear(c); // Return the first modified index. return i - 1; } // Join case #5: steal from next sibling branch if (!isLeaf && hasExtra && hasNext) { // Cast the children to branches. let c = child as BranchNode; let s = sibling as BranchNode; // Steal a child from the next sibling. c.children.push(s.children.shift()!); // Steal an item from the next sibling. c.items.push(s.items.shift()!); // Update the branch items. node.items[i + 1] = s.items[0]; // Update the sibling sizes. updateSizes(c, c.width - 1); updateSizes(s, 0); // Return the first modified index. return i; } // Join case #6: steal from previous sibling branch if (!isLeaf && hasExtra && !hasNext) { // Cast the children to branches. let c = child as BranchNode; let s = sibling as BranchNode; // Steal a child from the previous sibling. c.children.unshift(s.children.pop()!); // Steal an item from the previous sibling. c.items.unshift(s.items.pop()!); // Update the branch items. node.items[i] = c.items[0]; // Update the sibling sizes. updateSizes(c, 0); updateSizes(s, s.width - 1); // Return the first modified index. return i - 1; } // Join case #7: merge with next sibling branch if (!isLeaf && !hasExtra && hasNext) { // Cast the children to branches. let c = child as BranchNode; let s = sibling as BranchNode; // Merge the children with the next sibling. s.children.unshift(...c.children); // Merge the items with the next sibling. s.items.unshift(...c.items); // Remove the old branch child. ArrayExt.removeAt(node.children, i); // Remove the stale branch item. ArrayExt.removeAt(node.items, i + 1); // Update the sibling sizes. updateSizes(s, 0); // Clear the original child but, not its children. c.children.length = 0; clear(c); // Return the first modified index. return i; } // Join case #8: merge with previous sibling branch if (!isLeaf && !hasExtra && !hasNext) { // Cast the children to branches. let c = child as BranchNode; let s = sibling as BranchNode; // Merge the children with the previous sibling. s.children.push(...c.children); // Merge the items with the previous sibling. s.items.push(...c.items); // Remove the old branch child. ArrayExt.removeAt(node.children, i); // Remove the stale branch item. ArrayExt.removeAt(node.items, i); // Update the sibling sizes. updateSizes(s, 0); // Clear the original child, but not its children. c.children.length = 0; clear(c); // Return the first modified index. return i - 1; } // One of the above cases must match. throw 'unreachable'; } } lumino-2021.12.13/packages/collections/src/index.ts000066400000000000000000000007771415564225700217760ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ export * from './bplustree'; export * from './linkedlist'; lumino-2021.12.13/packages/collections/src/linkedlist.ts000066400000000000000000000412231415564225700230200ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { each, IIterable, IIterator, IRetroable, IterableOrArrayLike } from '@lumino/algorithm'; /** * A generic doubly-linked list. */ export class LinkedList implements IIterable, IRetroable { /** * Whether the list is empty. * * #### Complexity * Constant. */ get isEmpty(): boolean { return this._size === 0; } /** * The size of the list. * * #### Complexity * `O(1)` * * #### Notes * This is equivalent to `length`. */ get size(): number { return this._size; } /** * The length of the list. * * #### Complexity * Constant. * * #### Notes * This is equivalent to `size`. * * This property is deprecated. */ get length(): number { return this._size; } /** * The first value in the list. * * This is `undefined` if the list is empty. * * #### Complexity * Constant. */ get first(): T | undefined { return this._first ? this._first.value : undefined; } /** * The last value in the list. * * This is `undefined` if the list is empty. * * #### Complexity * Constant. */ get last(): T | undefined { return this._last ? this._last.value : undefined; } /** * The first node in the list. * * This is `null` if the list is empty. * * #### Complexity * Constant. */ get firstNode(): LinkedList.INode | null { return this._first; } /** * The last node in the list. * * This is `null` if the list is empty. * * #### Complexity * Constant. */ get lastNode(): LinkedList.INode | null { return this._last; } /** * Create an iterator over the values in the list. * * @returns A new iterator starting with the first value. * * #### Complexity * Constant. */ iter(): IIterator { return new LinkedList.ForwardValueIterator(this._first); } /** * Create a reverse iterator over the values in the list. * * @returns A new iterator starting with the last value. * * #### Complexity * Constant. */ retro(): IIterator { return new LinkedList.RetroValueIterator(this._last); } /** * Create an iterator over the nodes in the list. * * @returns A new iterator starting with the first node. * * #### Complexity * Constant. */ nodes(): IIterator> { return new LinkedList.ForwardNodeIterator(this._first); } /** * Create a reverse iterator over the nodes in the list. * * @returns A new iterator starting with the last node. * * #### Complexity * Constant. */ retroNodes(): IIterator> { return new LinkedList.RetroNodeIterator(this._last); } /** * Assign new values to the list, replacing all current values. * * @param values - The values to assign to the list. * * #### Complexity * Linear. */ assign(values: IterableOrArrayLike): void { this.clear(); each(values, value => { this.addLast(value); }); } /** * Add a value to the end of the list. * * @param value - The value to add to the end of the list. * * #### Complexity * Constant. * * #### Notes * This is equivalent to `addLast`. */ push(value: T): void { this.addLast(value); } /** * Remove and return the value at the end of the list. * * @returns The removed value, or `undefined` if the list is empty. * * #### Complexity * Constant. * * #### Notes * This is equivalent to `removeLast`. */ pop(): T | undefined { return this.removeLast(); } /** * Add a value to the beginning of the list. * * @param value - The value to add to the beginning of the list. * * #### Complexity * Constant. * * #### Notes * This is equivalent to `addFirst`. */ shift(value: T): void { this.addFirst(value); } /** * Remove and return the value at the beginning of the list. * * @returns The removed value, or `undefined` if the list is empty. * * #### Complexity * Constant. * * #### Notes * This is equivalent to `removeFirst`. */ unshift(): T | undefined { return this.removeFirst(); } /** * Add a value to the beginning of the list. * * @param value - The value to add to the beginning of the list. * * @returns The list node which holds the value. * * #### Complexity * Constant. */ addFirst(value: T): LinkedList.INode { let node = new Private.LinkedListNode(this, value); if (!this._first) { this._first = node; this._last = node; } else { node.next = this._first; this._first.prev = node; this._first = node; } this._size++; return node; } /** * Add a value to the end of the list. * * @param value - The value to add to the end of the list. * * @returns The list node which holds the value. * * #### Complexity * Constant. */ addLast(value: T): LinkedList.INode { let node = new Private.LinkedListNode(this, value); if (!this._last) { this._first = node; this._last = node; } else { node.prev = this._last; this._last.next = node; this._last = node; } this._size++; return node; } /** * Insert a value before a specific node in the list. * * @param value - The value to insert before the reference node. * * @param ref - The reference node of interest. If this is `null`, * the value will be added to the beginning of the list. * * @returns The list node which holds the value. * * #### Notes * The reference node must be owned by the list. * * #### Complexity * Constant. */ insertBefore(value: T, ref: LinkedList.INode | null): LinkedList.INode { if (!ref || ref === this._first) { return this.addFirst(value); } if (!(ref instanceof Private.LinkedListNode) || ref.list !== this) { throw new Error('Reference node is not owned by the list.'); } let node = new Private.LinkedListNode(this, value); let _ref = ref as Private.LinkedListNode; let prev = _ref.prev!; node.next = _ref; node.prev = prev; _ref.prev = node; prev.next = node; this._size++; return node; } /** * Insert a value after a specific node in the list. * * @param value - The value to insert after the reference node. * * @param ref - The reference node of interest. If this is `null`, * the value will be added to the end of the list. * * @returns The list node which holds the value. * * #### Notes * The reference node must be owned by the list. * * #### Complexity * Constant. */ insertAfter(value: T, ref: LinkedList.INode | null): LinkedList.INode { if (!ref || ref === this._last) { return this.addLast(value); } if (!(ref instanceof Private.LinkedListNode) || ref.list !== this) { throw new Error('Reference node is not owned by the list.'); } let node = new Private.LinkedListNode(this, value); let _ref = ref as Private.LinkedListNode; let next = _ref.next!; node.next = next; node.prev = _ref; _ref.next = node; next.prev = node; this._size++; return node; } /** * Remove and return the value at the beginning of the list. * * @returns The removed value, or `undefined` if the list is empty. * * #### Complexity * Constant. */ removeFirst(): T | undefined { let node = this._first; if (!node) { return undefined; } if (node === this._last) { this._first = null; this._last = null; } else { this._first = node.next; this._first!.prev = null; } node.list = null; node.next = null; node.prev = null; this._size--; return node.value; } /** * Remove and return the value at the end of the list. * * @returns The removed value, or `undefined` if the list is empty. * * #### Complexity * Constant. */ removeLast(): T | undefined { let node = this._last; if (!node) { return undefined; } if (node === this._first) { this._first = null; this._last = null; } else { this._last = node.prev; this._last!.next = null; } node.list = null; node.next = null; node.prev = null; this._size--; return node.value; } /** * Remove a specific node from the list. * * @param node - The node to remove from the list. * * #### Complexity * Constant. * * #### Notes * The node must be owned by the list. */ removeNode(node: LinkedList.INode): void { if (!(node instanceof Private.LinkedListNode) || node.list !== this) { throw new Error('Node is not owned by the list.'); } let _node = node as Private.LinkedListNode; if (_node === this._first && _node === this._last) { this._first = null; this._last = null; } else if (_node === this._first) { this._first = _node.next; this._first!.prev = null; } else if (_node === this._last) { this._last = _node.prev; this._last!.next = null; } else { _node.next!.prev = _node.prev; _node.prev!.next = _node.next; } _node.list = null; _node.next = null; _node.prev = null; this._size--; } /** * Remove all values from the list. * * #### Complexity * Linear. */ clear(): void { let node = this._first; while (node) { let next = node.next; node.list = null; node.prev = null; node.next = null; node = next; } this._first = null; this._last = null; this._size = 0; } private _first: Private.LinkedListNode | null = null; private _last: Private.LinkedListNode | null = null; private _size = 0; } /** * The namespace for the `LinkedList` class statics. */ export namespace LinkedList { /** * An object which represents a node in a linked list. * * #### Notes * User code will not create linked list nodes directly. Nodes * are created automatically when values are added to a list. */ export interface INode { /** * The linked list which created and owns the node. * * This will be `null` when the node is removed from the list. */ readonly list: LinkedList | null; /** * The next node in the list. * * This will be `null` when the node is the last node in the list * or when the node is removed from the list. */ readonly next: INode | null; /** * The previous node in the list. * * This will be `null` when the node is the first node in the list * or when the node is removed from the list. */ readonly prev: INode | null; /** * The user value stored in the node. */ readonly value: T; } /** * Create a linked list from an iterable of values. * * @param values - The iterable or array-like object of interest. * * @returns A new linked list initialized with the given values. * * #### Complexity * Linear. */ export function from(values: IterableOrArrayLike): LinkedList { let list = new LinkedList(); list.assign(values); return list; } /** * A forward iterator for values in a linked list. */ export class ForwardValueIterator implements IIterator { /** * Construct a forward value iterator. * * @param node - The first node in the list. */ constructor(node: INode | null) { this._node = node; } /** * Get an iterator over the object's values. * * @returns An iterator which yields the object's values. */ iter(): IIterator { return this; } /** * Create an independent clone of the iterator. * * @returns A new independent clone of the iterator. */ clone(): IIterator { return new ForwardValueIterator(this._node); } /** * Get the next value from the iterator. * * @returns The next value from the iterator, or `undefined`. */ next(): T | undefined { if (!this._node) { return undefined; } let node = this._node; this._node = node.next; return node.value; } private _node: INode | null; } /** * A reverse iterator for values in a linked list. */ export class RetroValueIterator implements IIterator { /** * Construct a retro value iterator. * * @param node - The last node in the list. */ constructor(node: INode | null) { this._node = node; } /** * Get an iterator over the object's values. * * @returns An iterator which yields the object's values. */ iter(): IIterator { return this; } /** * Create an independent clone of the iterator. * * @returns A new independent clone of the iterator. */ clone(): IIterator { return new RetroValueIterator(this._node); } /** * Get the next value from the iterator. * * @returns The next value from the iterator, or `undefined`. */ next(): T | undefined { if (!this._node) { return undefined; } let node = this._node; this._node = node.prev; return node.value; } private _node: INode | null; } /** * A forward iterator for nodes in a linked list. */ export class ForwardNodeIterator implements IIterator> { /** * Construct a forward node iterator. * * @param node - The first node in the list. */ constructor(node: INode | null) { this._node = node; } /** * Get an iterator over the object's values. * * @returns An iterator which yields the object's values. */ iter(): IIterator> { return this; } /** * Create an independent clone of the iterator. * * @returns A new independent clone of the iterator. */ clone(): IIterator> { return new ForwardNodeIterator(this._node); } /** * Get the next value from the iterator. * * @returns The next value from the iterator, or `undefined`. */ next(): INode | undefined { if (!this._node) { return undefined; } let node = this._node; this._node = node.next; return node; } private _node: INode | null; } /** * A reverse iterator for nodes in a linked list. */ export class RetroNodeIterator implements IIterator> { /** * Construct a retro node iterator. * * @param node - The last node in the list. */ constructor(node: INode | null) { this._node = node; } /** * Get an iterator over the object's values. * * @returns An iterator which yields the object's values. */ iter(): IIterator> { return this; } /** * Create an independent clone of the iterator. * * @returns A new independent clone of the iterator. */ clone(): IIterator> { return new RetroNodeIterator(this._node); } /** * Get the next value from the iterator. * * @returns The next value from the iterator, or `undefined`. */ next(): INode | undefined { if (!this._node) { return undefined; } let node = this._node; this._node = node.prev; return node; } private _node: INode | null; } } /** * The namespace for the module implementation details. */ namespace Private { /** * The internal linked list node implementation. */ export class LinkedListNode { /** * The linked list which created and owns the node. */ list: LinkedList | null = null; /** * The next node in the list. */ next: LinkedListNode | null = null; /** * The previous node in the list. */ prev: LinkedListNode | null = null; /** * The user value stored in the node. */ readonly value: T; /** * Construct a new linked list node. * * @param list - The list which owns the node. * * @param value - The value for the link. */ constructor(list: LinkedList, value: T) { this.list = list; this.value = value; } } } lumino-2021.12.13/packages/collections/tdoptions.json000066400000000000000000000003271415564225700224350ustar00rootroot00000000000000{ "excludeNotExported": true, "mode": "file", "target": "es5", "module": "es5", "out": "../../docs/source/api/collections", "baseUrl": ".", "paths": { "@lumino/*": ["node_modules/@lumino/*"] } } lumino-2021.12.13/packages/collections/tests/000077500000000000000000000000001415564225700206575ustar00rootroot00000000000000lumino-2021.12.13/packages/collections/tests/karma.conf.js000066400000000000000000000005051415564225700232340ustar00rootroot00000000000000module.exports = function (config) { config.set({ basePath: '.', frameworks: ['mocha'], reporters: ['mocha'], files: ['build/bundle.test.js'], port: 9876, colors: true, singleRun: true, browserNoActivityTimeout: 30000, failOnEmptyTestSuite: false, logLevel: config.LOG_INFO }); }; lumino-2021.12.13/packages/collections/tests/src/000077500000000000000000000000001415564225700214465ustar00rootroot00000000000000lumino-2021.12.13/packages/collections/tests/src/index.spec.ts000066400000000000000000000007401415564225700240570ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2016, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import './linkedlist.spec'; lumino-2021.12.13/packages/collections/tests/src/linkedlist.spec.ts000066400000000000000000000510041415564225700251110ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { expect } from 'chai'; import { find, map, toArray } from '@lumino/algorithm'; import { LinkedList } from '@lumino/collections'; describe('@lumino/collections', () => { describe('LinkedList', () => { describe('#constructor()', () => { let list = new LinkedList(); expect(list).to.be.an.instanceof(LinkedList); }); describe('#isEmpty', () => { it('should be `true` for an empty list', () => { let list = new LinkedList(); expect(list.isEmpty).to.equal(true); }); it('should be `false` for a non-empty list', () => { let data = [0, 1, 2, 3, 4, 5]; let list = LinkedList.from(data); expect(list.isEmpty).to.equal(false); }); }); describe('#length', () => { it('should be `0` for an empty list', () => { let list = new LinkedList(); expect(list.length).to.equal(0); }); it('should equal the number of items in a list', () => { let data = [0, 1, 2, 3, 4, 5]; let list = LinkedList.from(data); expect(list.length).to.equal(data.length); }); }); describe('#first', () => { it('should be the first value in the list', () => { let data = [0, 1, 2, 3, 4, 5]; let list = LinkedList.from(data); expect(list.first).to.equal(data[0]); }); it('should be `undefined` if the list is empty', () => { let list = new LinkedList(); expect(list.first).to.equal(undefined); }); }); describe('#last', () => { it('should be the last value in the list', () => { let data = [0, 1, 2, 3, 4, 5]; let list = LinkedList.from(data); expect(list.last).to.equal(data[data.length - 1]); }); it('should be `undefined` if the list is empty', () => { let list = new LinkedList(); expect(list.last).to.equal(undefined); }); }); describe('#firstNode', () => { it('should be the first node in the list', () => { let data = [0, 1, 2, 3, 4, 5]; let list = LinkedList.from(data); expect(list.firstNode!.value).to.equal(data[0]); }); it('should be `null` if the list is empty', () => { let list = new LinkedList(); expect(list.firstNode).to.equal(null); }); }); describe('#lastNode', () => { it('should be the last node in the list', () => { let data = [0, 1, 2, 3, 4, 5]; let list = LinkedList.from(data); expect(list.lastNode!.value).to.equal(data[data.length - 1]); }); it('should be `null` if the list is empty', () => { let list = new LinkedList(); expect(list.lastNode).to.equal(null); }); }); describe('#iter()', () => { it('should return an iterator over the list values', () => { let data = [0, 1, 2, 3, 4, 5]; let list = LinkedList.from(data); let it1 = list.iter(); let it2 = it1.clone(); expect(it1.iter()).to.equal(it1); expect(it2.iter()).to.equal(it2); expect(toArray(it1)).to.deep.equal(data); expect(toArray(it2)).to.deep.equal(data); }); }); describe('#retro()', () => { it('should return a reverse iterator over the list values', () => { let data = [0, 1, 2, 3, 4, 5]; let reversed = data.slice().reverse(); let list = LinkedList.from(data); let it1 = list.retro(); let it2 = it1.clone(); expect(it1.iter()).to.equal(it1); expect(it2.iter()).to.equal(it2); expect(toArray(it1)).to.deep.equal(reversed); expect(toArray(it2)).to.deep.equal(reversed); }); }); describe('#nodes()', () => { it('should return an iterator over the list nodes', () => { let data = [0, 1, 2, 3, 4, 5]; let list = LinkedList.from(data); let it1 = list.nodes(); let it2 = it1.clone(); let v1 = map(it1, n => n.value); let v2 = map(it2, n => n.value); expect(it1.iter()).to.equal(it1); expect(it2.iter()).to.equal(it2); expect(toArray(v1)).to.deep.equal(data); expect(toArray(v2)).to.deep.equal(data); }); }); describe('#retroNodes()', () => { it('should return a reverse iterator over the list nodes', () => { let data = [0, 1, 2, 3, 4, 5]; let reversed = data.slice().reverse(); let list = LinkedList.from(data); let it1 = list.retroNodes(); let it2 = it1.clone(); let v1 = map(it1, n => n.value); let v2 = map(it2, n => n.value); expect(it1.iter()).to.equal(it1); expect(it2.iter()).to.equal(it2); expect(toArray(v1)).to.deep.equal(reversed); expect(toArray(v2)).to.deep.equal(reversed); }); }); describe('#addFirst()', () => { it('should add a value to the beginning of the list', () => { let list = new LinkedList(); expect(list.isEmpty).to.equal(true); expect(list.length).to.equal(0); expect(list.first).to.equal(undefined); expect(list.last).to.equal(undefined); let n1 = list.addFirst(99); expect(list.isEmpty).to.equal(false); expect(list.length).to.equal(1); expect(list.first).to.equal(99); expect(list.last).to.equal(99); let n2 = list.addFirst(42); expect(list.isEmpty).to.equal(false); expect(list.length).to.equal(2); expect(list.first).to.equal(42); expect(list.last).to.equal(99); let n3 = list.addFirst(7); expect(list.isEmpty).to.equal(false); expect(list.length).to.equal(3); expect(list.first).to.equal(7); expect(list.last).to.equal(99); expect(toArray(list)).to.deep.equal([7, 42, 99]); expect(n1.list).to.equal(list); expect(n1.next).to.equal(null); expect(n1.prev).to.equal(n2); expect(n1.value).to.equal(99); expect(n2.list).to.equal(list); expect(n2.next).to.equal(n1); expect(n2.prev).to.equal(n3); expect(n2.value).to.equal(42); expect(n3.list).to.equal(list); expect(n3.next).to.equal(n2); expect(n3.prev).to.equal(null); expect(n3.value).to.equal(7); }); }); describe('#addLast()', () => { it('should add a value to the end of the list', () => { let list = new LinkedList(); expect(list.isEmpty).to.equal(true); expect(list.length).to.equal(0); expect(list.first).to.equal(undefined); expect(list.last).to.equal(undefined); let n1 = list.addLast(99); expect(list.isEmpty).to.equal(false); expect(list.length).to.equal(1); expect(list.first).to.equal(99); expect(list.last).to.equal(99); let n2 = list.addLast(42); expect(list.isEmpty).to.equal(false); expect(list.length).to.equal(2); expect(list.first).to.equal(99); expect(list.last).to.equal(42); let n3 = list.addLast(7); expect(list.isEmpty).to.equal(false); expect(list.length).to.equal(3); expect(list.first).to.equal(99); expect(list.last).to.equal(7); expect(toArray(list)).to.deep.equal([99, 42, 7]); expect(n1.list).to.equal(list); expect(n1.next).to.equal(n2); expect(n1.prev).to.equal(null); expect(n1.value).to.equal(99); expect(n2.list).to.equal(list); expect(n2.next).to.equal(n3); expect(n2.prev).to.equal(n1); expect(n2.value).to.equal(42); expect(n3.list).to.equal(list); expect(n3.next).to.equal(null); expect(n3.prev).to.equal(n2); expect(n3.value).to.equal(7); }); }); describe('#insertBefore()', () => { it('should insert a value before the given reference node', () => { let list = LinkedList.from([0, 1, 2, 3]); let n1 = find(list.nodes(), n => n.value === 2)!; let n2 = list.insertBefore(7, n1); let n3 = list.insertBefore(8, n2); let n4 = list.insertBefore(9, null); let n5 = find(list.nodes(), n => n.value === 1); let n6 = find(list.nodes(), n => n.value === 0); expect(list.isEmpty).to.equal(false); expect(list.length).to.equal(7); expect(list.first).to.equal(9); expect(list.last).to.equal(3); expect(toArray(list)).to.deep.equal([9, 0, 1, 8, 7, 2, 3]); expect(n1.list).to.equal(list); expect(n1.next).to.equal(list.lastNode); expect(n1.prev).to.equal(n2); expect(n1.value).to.equal(2); expect(n2.list).to.equal(list); expect(n2.next).to.equal(n1); expect(n2.prev).to.equal(n3); expect(n2.value).to.equal(7); expect(n3.list).to.equal(list); expect(n3.next).to.equal(n2); expect(n3.prev).to.equal(n5); expect(n3.value).to.equal(8); expect(n4.list).to.equal(list); expect(n4.next).to.equal(n6); expect(n4.prev).to.equal(null); expect(n4.value).to.equal(9); }); it('should throw an error if the reference node is invalid', () => { let list1 = LinkedList.from([0, 1, 2, 3]); let list2 = LinkedList.from([0, 1, 2, 3]); let insert = () => { list2.insertBefore(4, list1.firstNode); }; expect(insert).to.throw(Error); }); }); describe('#insertAfter()', () => { it('should insert a value after the given reference node', () => { let list = LinkedList.from([0, 1, 2, 3]); let n1 = find(list.nodes(), n => n.value === 2)!; let n2 = list.insertAfter(7, n1); let n3 = list.insertAfter(8, n2); let n4 = list.insertAfter(9, null); let n5 = find(list.nodes(), n => n.value === 1); let n6 = find(list.nodes(), n => n.value === 3); expect(list.isEmpty).to.equal(false); expect(list.length).to.equal(7); expect(list.first).to.equal(0); expect(list.last).to.equal(9); expect(toArray(list)).to.deep.equal([0, 1, 2, 7, 8, 3, 9]); expect(n1.list).to.equal(list); expect(n1.next).to.equal(n2); expect(n1.prev).to.equal(n5); expect(n1.value).to.equal(2); expect(n2.list).to.equal(list); expect(n2.next).to.equal(n3); expect(n2.prev).to.equal(n1); expect(n2.value).to.equal(7); expect(n3.list).to.equal(list); expect(n3.next).to.equal(n6); expect(n3.prev).to.equal(n2); expect(n3.value).to.equal(8); expect(n4.list).to.equal(list); expect(n4.next).to.equal(null); expect(n4.prev).to.equal(n6); expect(n4.value).to.equal(9); }); it('should throw an error if the reference node is invalid', () => { let list1 = LinkedList.from([0, 1, 2, 3]); let list2 = LinkedList.from([0, 1, 2, 3]); let insert = () => { list2.insertAfter(4, list1.firstNode); }; expect(insert).to.throw(Error); }); }); describe('#removeFirst()', () => { it('should remove the first value from the list', () => { let list = LinkedList.from([0, 1, 2, 3]); expect(list.isEmpty).to.equal(false); expect(list.length).to.equal(4); expect(list.first).to.equal(0); expect(list.last).to.equal(3); expect(toArray(list)).to.deep.equal([0, 1, 2, 3]); let v1 = list.removeFirst(); expect(list.isEmpty).to.equal(false); expect(list.length).to.equal(3); expect(list.first).to.equal(1); expect(list.last).to.equal(3); expect(toArray(list)).to.deep.equal([1, 2, 3]); let v2 = list.removeFirst(); expect(list.isEmpty).to.equal(false); expect(list.length).to.equal(2); expect(list.first).to.equal(2); expect(list.last).to.equal(3); expect(toArray(list)).to.deep.equal([2, 3]); let v3 = list.removeFirst(); expect(list.isEmpty).to.equal(false); expect(list.length).to.equal(1); expect(list.first).to.equal(3); expect(list.last).to.equal(3); expect(toArray(list)).to.deep.equal([3]); let v4 = list.removeFirst(); expect(list.isEmpty).to.equal(true); expect(list.length).to.equal(0); expect(list.first).to.equal(undefined); expect(list.last).to.equal(undefined); expect(toArray(list)).to.deep.equal([]); let v5 = list.removeFirst(); expect(list.isEmpty).to.equal(true); expect(list.length).to.equal(0); expect(list.first).to.equal(undefined); expect(list.last).to.equal(undefined); expect(toArray(list)).to.deep.equal([]); expect(v1).to.equal(0); expect(v2).to.equal(1); expect(v3).to.equal(2); expect(v4).to.equal(3); expect(v5).to.equal(undefined); }); }); describe('#removeLast()', () => { it('should remove the last value from the list', () => { let list = LinkedList.from([0, 1, 2, 3]); expect(list.isEmpty).to.equal(false); expect(list.length).to.equal(4); expect(list.first).to.equal(0); expect(list.last).to.equal(3); expect(toArray(list)).to.deep.equal([0, 1, 2, 3]); let v1 = list.removeLast(); expect(list.isEmpty).to.equal(false); expect(list.length).to.equal(3); expect(list.first).to.equal(0); expect(list.last).to.equal(2); expect(toArray(list)).to.deep.equal([0, 1, 2]); let v2 = list.removeLast(); expect(list.isEmpty).to.equal(false); expect(list.length).to.equal(2); expect(list.first).to.equal(0); expect(list.last).to.equal(1); expect(toArray(list)).to.deep.equal([0, 1]); let v3 = list.removeLast(); expect(list.isEmpty).to.equal(false); expect(list.length).to.equal(1); expect(list.first).to.equal(0); expect(list.last).to.equal(0); expect(toArray(list)).to.deep.equal([0]); let v4 = list.removeLast(); expect(list.isEmpty).to.equal(true); expect(list.length).to.equal(0); expect(list.first).to.equal(undefined); expect(list.last).to.equal(undefined); expect(toArray(list)).to.deep.equal([]); let v5 = list.removeLast(); expect(list.isEmpty).to.equal(true); expect(list.length).to.equal(0); expect(list.first).to.equal(undefined); expect(list.last).to.equal(undefined); expect(toArray(list)).to.deep.equal([]); expect(v1).to.equal(3); expect(v2).to.equal(2); expect(v3).to.equal(1); expect(v4).to.equal(0); expect(v5).to.equal(undefined); }); }); describe('#removeNode()', () => { it('should remove the specified node from the list', () => { let list = LinkedList.from([0, 1, 2, 3]); expect(list.isEmpty).to.equal(false); expect(list.length).to.equal(4); expect(list.first).to.equal(0); expect(list.last).to.equal(3); expect(toArray(list)).to.deep.equal([0, 1, 2, 3]); let n1 = find(list.nodes(), n => n.value === 2)!; list.removeNode(n1); expect(list.isEmpty).to.equal(false); expect(list.length).to.equal(3); expect(list.first).to.equal(0); expect(list.last).to.equal(3); expect(toArray(list)).to.deep.equal([0, 1, 3]); expect(n1.list).to.equal(null); expect(n1.next).to.equal(null); expect(n1.prev).to.equal(null); expect(n1.value).to.equal(2); let n2 = find(list.nodes(), n => n.value === 3)!; list.removeNode(n2); expect(list.isEmpty).to.equal(false); expect(list.length).to.equal(2); expect(list.first).to.equal(0); expect(list.last).to.equal(1); expect(toArray(list)).to.deep.equal([0, 1]); expect(n2.list).to.equal(null); expect(n2.next).to.equal(null); expect(n2.prev).to.equal(null); expect(n2.value).to.equal(3); let n3 = find(list.nodes(), n => n.value === 0)!; list.removeNode(n3); expect(list.isEmpty).to.equal(false); expect(list.length).to.equal(1); expect(list.first).to.equal(1); expect(list.last).to.equal(1); expect(toArray(list)).to.deep.equal([1]); expect(n3.list).to.equal(null); expect(n3.next).to.equal(null); expect(n3.prev).to.equal(null); expect(n3.value).to.equal(0); let n4 = find(list.nodes(), n => n.value === 1)!; list.removeNode(n4); expect(list.isEmpty).to.equal(true); expect(list.length).to.equal(0); expect(list.first).to.equal(undefined); expect(list.last).to.equal(undefined); expect(toArray(list)).to.deep.equal([]); expect(n4.list).to.equal(null); expect(n4.next).to.equal(null); expect(n4.prev).to.equal(null); expect(n4.value).to.equal(1); }); }); describe('#clear()', () => { it('should remove all values from the list', () => { let list = LinkedList.from([0, 1, 2, 3]); expect(list.isEmpty).to.equal(false); expect(list.length).to.equal(4); expect(list.first).to.equal(0); expect(list.last).to.equal(3); expect(toArray(list)).to.deep.equal([0, 1, 2, 3]); list.clear(); expect(list.isEmpty).to.equal(true); expect(list.length).to.equal(0); expect(list.first).to.equal(undefined); expect(list.last).to.equal(undefined); expect(toArray(list)).to.deep.equal([]); }); }); describe('.from()', () => { it('should initialize a list from an iterable', () => { let list1 = LinkedList.from([0, 1, 2, 3]); let list2 = LinkedList.from(list1); expect(list2.isEmpty).to.equal(false); expect(list2.length).to.equal(4); expect(list2.first).to.equal(0); expect(list2.last).to.equal(3); expect(toArray(list2)).to.deep.equal([0, 1, 2, 3]); }); }); describe('.ForwardValueIterator', () => { it('should create a forward iterator over the values', () => { let list = LinkedList.from([0, 1, 2, 3, 4]); let n = find(list.nodes(), n => n.value === 2)!; let it1 = new LinkedList.ForwardValueIterator(n); let it2 = it1.clone(); expect(it1.iter()).to.equal(it1); expect(it2.iter()).to.equal(it2); expect(toArray(it1)).to.deep.equal([2, 3, 4]); expect(toArray(it2)).to.deep.equal([2, 3, 4]); }); }); describe('.RetroValueIterator', () => { it('should create a reverse iterator over the values', () => { let list = LinkedList.from([0, 1, 2, 3, 4]); let n = find(list.nodes(), n => n.value === 2)!; let it1 = new LinkedList.RetroValueIterator(n); let it2 = it1.clone(); expect(it1.iter()).to.equal(it1); expect(it2.iter()).to.equal(it2); expect(toArray(it1)).to.deep.equal([2, 1, 0]); expect(toArray(it2)).to.deep.equal([2, 1, 0]); }); }); describe('.ForwardNodeIterator', () => { it('should create a forward iterator over the nodes', () => { let list = LinkedList.from([0, 1, 2, 3, 4]); let n = find(list.nodes(), n => n.value === 2)!; let it1 = new LinkedList.ForwardNodeIterator(n); let it2 = it1.clone(); let v1 = map(it1, n => n.value); let v2 = map(it2, n => n.value); expect(it1.iter()).to.equal(it1); expect(it2.iter()).to.equal(it2); expect(toArray(v1)).to.deep.equal([2, 3, 4]); expect(toArray(v2)).to.deep.equal([2, 3, 4]); }); }); describe('.RetroNodeIterator', () => { it('should create a reverse iterator over the nodes', () => { let list = LinkedList.from([0, 1, 2, 3, 4]); let n = find(list.nodes(), n => n.value === 2)!; let it1 = new LinkedList.RetroNodeIterator(n); let it2 = it1.clone(); let v1 = map(it1, n => n.value); let v2 = map(it2, n => n.value); expect(it1.iter()).to.equal(it1); expect(it2.iter()).to.equal(it2); expect(toArray(v1)).to.deep.equal([2, 1, 0]); expect(toArray(v2)).to.deep.equal([2, 1, 0]); }); }); }); }); lumino-2021.12.13/packages/collections/tests/tsconfig.json000066400000000000000000000006631415564225700233730ustar00rootroot00000000000000{ "compilerOptions": { "declaration": false, "noImplicitAny": true, "noEmitOnError": true, "noUnusedLocals": true, "strictNullChecks": true, "module": "commonjs", "moduleResolution": "node", "target": "ES5", "outDir": "build", "lib": ["ES5"], "types": ["chai", "mocha"], "rootDir": "src" }, "include": ["src/*"], "references": [ { "path": "../../algorithm" } ] } lumino-2021.12.13/packages/collections/tests/webpack.config.js000066400000000000000000000003061415564225700240740ustar00rootroot00000000000000var path = require('path'); module.exports = { entry: './build/index.spec.js', mode: 'development', output: { filename: './build/bundle.test.js', path: path.resolve(__dirname) } }; lumino-2021.12.13/packages/collections/tsconfig.json000066400000000000000000000010371415564225700222250ustar00rootroot00000000000000{ "compilerOptions": { "composite": true, "sourceMap": true, "declaration": true, "declarationDir": "./types", "declarationMap": true, "noImplicitAny": true, "noEmitOnError": true, "noUnusedLocals": true, "strictNullChecks": true, "module": "ES6", "moduleResolution": "node", "target": "ES5", "outDir": "lib", "lib": ["ES5"], "importHelpers": true, "types": [], "rootDir": "src" }, "include": ["src/*"], "references": [ { "path": "../algorithm" } ] } lumino-2021.12.13/packages/commands/000077500000000000000000000000001415564225700170005ustar00rootroot00000000000000lumino-2021.12.13/packages/commands/api-extractor.json000066400000000000000000000014611415564225700224570ustar00rootroot00000000000000/** * Config file for API Extractor. For more info, please visit: https://api-extractor.com */ { "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", /** * Optionally specifies another JSON config file that this file extends from. This provides a way for * standard settings to be shared across multiple projects. * * If the path starts with "./" or "../", the path is resolved relative to the folder of the file that contains * the "extends" field. Otherwise, the first path segment is interpreted as an NPM package name, and will be * resolved using NodeJS require(). * * SUPPORTED TOKENS: none * DEFAULT VALUE: "" */ "extends": "../../api-extractor-base.json" // "extends": "my-package/include/api-extractor-base.json" } lumino-2021.12.13/packages/commands/package.json000066400000000000000000000066261415564225700213000ustar00rootroot00000000000000{ "name": "@lumino/commands", "version": "1.19.1", "description": "Lumino Commands", "homepage": "https://github.com/jupyterlab/lumino", "bugs": { "url": "https://github.com/jupyterlab/lumino/issues" }, "repository": { "type": "git", "url": "https://github.com/jupyterlab/lumino.git" }, "license": "BSD-3-Clause", "author": "S. Chris Colbert ", "contributors": [ "A. Darian ", "Dave Willmer ", "S. Chris Colbert ", "Steven Silvester " ], "main": "dist/index.js", "jsdelivr": "dist/index.min.js", "unpkg": "dist/index.min.js", "module": "dist/index.es6", "types": "types/index.d.ts", "files": [ "dist/*", "src/*", "types/*" ], "scripts": { "api": "api-extractor run --local --verbose", "build": "npm run build:src && rollup -c", "build:src": "tsc --build", "build:test": "tsc --build tests && cd tests && webpack", "clean": "rimraf ./lib && rimraf *.tsbuildinfo && rimraf ./types && rimraf ./dist", "clean:test": "rimraf tests/build", "docs": "typedoc --options tdoptions.json src", "minimize": "terser dist/index.js -c -m --source-map \"content='dist/index.js.map',url='index.min.js.map'\" -o dist/index.min.js", "test": "npm run test:firefox-headless", "test:chrome": "npm run test:nobrowser -- --browsers=Chrome", "test:chrome-headless": "npm run test:nobrowser -- --browsers=ChromeHeadless", "test:debug": "npm run test:debug:firefox", "test:debug:chrome": "npm run test:debug:nobrowser -- --browsers=Chrome", "test:debug:chrome-headless": "npm run test:debug:nobrowser -- --browsers=ChromeHeadless", "test:debug:firefox": "npm run test:debug:nobrowser -- --browsers=Firefox", "test:debug:firefox-headless": "npm run test:debug:nobrowser -- --browsers=FirefoxHeadless", "test:debug:nobrowser": "cd tests && karma start --singleRun=false --debug=true --browserNoActivityTimeout=10000000 --browserDisconnectTimeout=10000000", "test:firefox": "npm run test:nobrowser -- --browsers=Firefox", "test:firefox-headless": "npm run test:nobrowser -- --browsers=FirefoxHeadless", "test:ie": "npm run test:nobrowser -- --browsers=IE", "test:nobrowser": "cd tests && karma start", "watch": "tsc --build --watch" }, "dependencies": { "@lumino/algorithm": "^1.9.1", "@lumino/coreutils": "^1.11.1", "@lumino/disposable": "^1.10.1", "@lumino/domutils": "^1.8.1", "@lumino/keyboard": "^1.8.1", "@lumino/signaling": "^1.10.1", "@lumino/virtualdom": "^1.14.1" }, "devDependencies": { "@microsoft/api-extractor": "^7.6.0", "@types/chai": "^3.4.35", "@types/mocha": "^2.2.39", "chai": "^4.3.4", "es6-promise": "^4.0.5", "karma": "^6.3.4", "karma-chrome-launcher": "^3.1.0", "karma-firefox-launcher": "^2.1.1", "karma-ie-launcher": "^1.0.0", "karma-mocha": "^2.0.1", "karma-mocha-reporter": "^2.2.5", "mocha": "^9.0.3", "rimraf": "^3.0.2", "rollup": "^2.56.0", "rollup-plugin-node-resolve": "^5.2.0", "rollup-plugin-postcss": "^4.0.0", "rollup-plugin-sourcemaps": "^0.6.3", "simulate-event": "^1.4.0", "terser": "^5.7.1", "tslib": "^2.3.0", "typedoc": "~0.15.0", "typescript": "~3.6.0", "webpack": "^4.41.3", "webpack-cli": "^3.3.10" }, "publishConfig": { "access": "public" } } lumino-2021.12.13/packages/commands/rollup.config.js000066400000000000000000000015231415564225700221200ustar00rootroot00000000000000import nodeResolve from 'rollup-plugin-node-resolve'; import sourcemaps from 'rollup-plugin-sourcemaps'; import postcss from 'rollup-plugin-postcss'; const pkg = require('./package.json'); const globals = id => id.indexOf('@lumino/') === 0 ? id.replace('@lumino/', 'lumino_') : id; export default [ { input: 'lib/index', external: id => pkg.dependencies && !!pkg.dependencies[id], output: [ { file: pkg.main, globals, format: 'umd', sourcemap: true, name: pkg.name }, { file: pkg.module + '.js', format: 'es', sourcemap: true, name: pkg.name } ], plugins: [ nodeResolve({ preferBuiltins: true }), sourcemaps(), postcss({ extensions: ['.css'], minimize: true }) ] } ]; lumino-2021.12.13/packages/commands/src/000077500000000000000000000000001415564225700175675ustar00rootroot00000000000000lumino-2021.12.13/packages/commands/src/index.ts000066400000000000000000001324521415564225700212550ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { ArrayExt } from '@lumino/algorithm'; import { JSONExt, ReadonlyJSONObject, ReadonlyPartialJSONObject } from '@lumino/coreutils'; import { DisposableDelegate, IDisposable } from '@lumino/disposable'; import { Platform, Selector } from '@lumino/domutils'; import { getKeyboardLayout } from '@lumino/keyboard'; import { ISignal, Signal } from '@lumino/signaling'; import { VirtualElement } from '@lumino/virtualdom'; /** * An object which manages a collection of commands. * * #### Notes * A command registry can be used to populate a variety of action-based * widgets, such as command palettes, menus, and toolbars. */ export class CommandRegistry { /** * A signal emitted when a command has changed. * * #### Notes * This signal is useful for visual representations of commands which * need to refresh when the state of a relevant command has changed. */ get commandChanged(): ISignal { return this._commandChanged; } /** * A signal emitted when a command has executed. * * #### Notes * Care should be taken when consuming this signal. The command system is used * by many components for many user actions. Handlers registered with this * signal must return quickly to ensure the overall application remains responsive. */ get commandExecuted(): ISignal { return this._commandExecuted; } /** * A signal emitted when a key binding is changed. */ get keyBindingChanged(): ISignal< this, CommandRegistry.IKeyBindingChangedArgs > { return this._keyBindingChanged; } /** * A read-only array of the key bindings in the registry. */ get keyBindings(): ReadonlyArray { return this._keyBindings; } /** * List the ids of the registered commands. * * @returns A new array of the registered command ids. */ listCommands(): string[] { return Object.keys(this._commands); } /** * Test whether a specific command is registered. * * @param id - The id of the command of interest. * * @returns `true` if the command is registered, `false` otherwise. */ hasCommand(id: string): boolean { return id in this._commands; } /** * Add a command to the registry. * * @param id - The unique id of the command. * * @param options - The options for the command. * * @returns A disposable which will remove the command. * * @throws An error if the given `id` is already registered. */ addCommand( id: string, options: CommandRegistry.ICommandOptions ): IDisposable { // Throw an error if the id is already registered. if (id in this._commands) { throw new Error(`Command '${id}' already registered.`); } // Add the command to the registry. this._commands[id] = Private.createCommand(options); // Emit the `commandChanged` signal. this._commandChanged.emit({ id, type: 'added' }); // Return a disposable which will remove the command. return new DisposableDelegate(() => { // Remove the command from the registry. delete this._commands[id]; // Emit the `commandChanged` signal. this._commandChanged.emit({ id, type: 'removed' }); }); } /** * Notify listeners that the state of a command has changed. * * @param id - The id of the command which has changed. If more than * one command has changed, this argument should be omitted. * * @throws An error if the given `id` is not registered. * * #### Notes * This method should be called by the command author whenever the * application state changes such that the results of the command * metadata functions may have changed. * * This will cause the `commandChanged` signal to be emitted. */ notifyCommandChanged(id?: string): void { if (id !== undefined && !(id in this._commands)) { throw new Error(`Command '${id}' is not registered.`); } this._commandChanged.emit({ id, type: id ? 'changed' : 'many-changed' }); } /** * Get the display label for a specific command. * * @param id - The id of the command of interest. * * @param args - The arguments for the command. * * @returns The display label for the command, or an empty string * if the command is not registered. */ label( id: string, args: ReadonlyPartialJSONObject = JSONExt.emptyObject ): string { let cmd = this._commands[id]; return cmd ? cmd.label.call(undefined, args) : ''; } /** * Get the mnemonic index for a specific command. * * @param id - The id of the command of interest. * * @param args - The arguments for the command. * * @returns The mnemonic index for the command, or `-1` if the * command is not registered. */ mnemonic( id: string, args: ReadonlyPartialJSONObject = JSONExt.emptyObject ): number { let cmd = this._commands[id]; return cmd ? cmd.mnemonic.call(undefined, args) : -1; } /** * Get the icon renderer for a specific command. * * DEPRECATED: if set to a string value, the .icon field will * function as an alias for the .iconClass field, for backwards * compatibility. In the future when this is removed, the default * return type will become undefined. * * @param id - The id of the command of interest. * * @param args - The arguments for the command. * * @returns The icon renderer for the command, or * an empty string if the command is not registered. */ icon( id: string, args: ReadonlyPartialJSONObject = JSONExt.emptyObject ): | VirtualElement.IRenderer | undefined /* */ | string /* */ { let cmd = this._commands[id]; return cmd ? cmd.icon.call(undefined, args) : /* */ '' /* */ /* undefined */; } /** * Get the icon class for a specific command. * * @param id - The id of the command of interest. * * @param args - The arguments for the command. * * @returns The icon class for the command, or an empty string if * the command is not registered. */ iconClass( id: string, args: ReadonlyPartialJSONObject = JSONExt.emptyObject ): string { let cmd = this._commands[id]; return cmd ? cmd.iconClass.call(undefined, args) : ''; } /** * Get the icon label for a specific command. * * @param id - The id of the command of interest. * * @param args - The arguments for the command. * * @returns The icon label for the command, or an empty string if * the command is not registered. */ iconLabel( id: string, args: ReadonlyPartialJSONObject = JSONExt.emptyObject ): string { let cmd = this._commands[id]; return cmd ? cmd.iconLabel.call(undefined, args) : ''; } /** * Get the short form caption for a specific command. * * @param id - The id of the command of interest. * * @param args - The arguments for the command. * * @returns The caption for the command, or an empty string if the * command is not registered. */ caption( id: string, args: ReadonlyPartialJSONObject = JSONExt.emptyObject ): string { let cmd = this._commands[id]; return cmd ? cmd.caption.call(undefined, args) : ''; } /** * Get the usage help text for a specific command. * * @param id - The id of the command of interest. * * @param args - The arguments for the command. * * @returns The usage text for the command, or an empty string if * the command is not registered. */ usage( id: string, args: ReadonlyPartialJSONObject = JSONExt.emptyObject ): string { let cmd = this._commands[id]; return cmd ? cmd.usage.call(undefined, args) : ''; } /** * Get the extra class name for a specific command. * * @param id - The id of the command of interest. * * @param args - The arguments for the command. * * @returns The class name for the command, or an empty string if * the command is not registered. */ className( id: string, args: ReadonlyPartialJSONObject = JSONExt.emptyObject ): string { let cmd = this._commands[id]; return cmd ? cmd.className.call(undefined, args) : ''; } /** * Get the dataset for a specific command. * * @param id - The id of the command of interest. * * @param args - The arguments for the command. * * @returns The dataset for the command, or an empty dataset if * the command is not registered. */ dataset( id: string, args: ReadonlyPartialJSONObject = JSONExt.emptyObject ): CommandRegistry.Dataset { let cmd = this._commands[id]; return cmd ? cmd.dataset.call(undefined, args) : {}; } /** * Test whether a specific command is enabled. * * @param id - The id of the command of interest. * * @param args - The arguments for the command. * * @returns A boolean indicating whether the command is enabled, * or `false` if the command is not registered. */ isEnabled( id: string, args: ReadonlyPartialJSONObject = JSONExt.emptyObject ): boolean { let cmd = this._commands[id]; return cmd ? cmd.isEnabled.call(undefined, args) : false; } /** * Test whether a specific command is toggled. * * @param id - The id of the command of interest. * * @param args - The arguments for the command. * * @returns A boolean indicating whether the command is toggled, * or `false` if the command is not registered. */ isToggled( id: string, args: ReadonlyPartialJSONObject = JSONExt.emptyObject ): boolean { let cmd = this._commands[id]; return cmd ? cmd.isToggled.call(undefined, args) : false; } /** * Test whether a specific command is toggleable. * * @param id - The id of the command of interest. * * @param args - The arguments for the command. * * @returns A boolean indicating whether the command is toggleable, * or `false` if the command is not registered. */ isToggleable( id: string, args: ReadonlyJSONObject = JSONExt.emptyObject ): boolean { let cmd = this._commands[id]; return cmd ? cmd.isToggleable : false; } /** * Test whether a specific command is visible. * * @param id - The id of the command of interest. * * @param args - The arguments for the command. * * @returns A boolean indicating whether the command is visible, * or `false` if the command is not registered. */ isVisible( id: string, args: ReadonlyPartialJSONObject = JSONExt.emptyObject ): boolean { let cmd = this._commands[id]; return cmd ? cmd.isVisible.call(undefined, args) : false; } /** * Execute a specific command. * * @param id - The id of the command of interest. * * @param args - The arguments for the command. * * @returns A promise which resolves with the result of the command. * * #### Notes * The promise will reject if the command throws an exception, * or if the command is not registered. */ execute( id: string, args: ReadonlyPartialJSONObject = JSONExt.emptyObject ): Promise { // Reject if the command is not registered. let cmd = this._commands[id]; if (!cmd) { return Promise.reject(new Error(`Command '${id}' not registered.`)); } // Execute the command and reject if an exception is thrown. let value: any; try { value = cmd.execute.call(undefined, args); } catch (err) { value = Promise.reject(err); } // Create the return promise which resolves the result. let result = Promise.resolve(value); // Emit the command executed signal. this._commandExecuted.emit({ id, args, result }); // Return the result promise to the caller. return result; } /** * Add a key binding to the registry. * * @param options - The options for creating the key binding. * * @returns A disposable which removes the added key binding. * * #### Notes * If multiple key bindings are registered for the same sequence, the * binding with the highest selector specificity is executed first. A * tie is broken by using the most recently added key binding. * * Ambiguous key bindings are resolved with a timeout. As an example, * suppose two key bindings are registered: one with the key sequence * `['Ctrl D']`, and another with `['Ctrl D', 'Ctrl W']`. If the user * presses `Ctrl D`, the first binding cannot be immediately executed * since the user may intend to complete the chord with `Ctrl W`. For * such cases, a timer is used to allow the chord to be completed. If * the chord is not completed before the timeout, the first binding * is executed. */ addKeyBinding(options: CommandRegistry.IKeyBindingOptions): IDisposable { // Create the binding for the given options. let binding = Private.createKeyBinding(options); // Add the key binding to the bindings array. this._keyBindings.push(binding); // Emit the `bindingChanged` signal. this._keyBindingChanged.emit({ binding, type: 'added' }); // Return a disposable which will remove the binding. return new DisposableDelegate(() => { // Remove the binding from the array. ArrayExt.removeFirstOf(this._keyBindings, binding); // Emit the `bindingChanged` signal. this._keyBindingChanged.emit({ binding, type: 'removed' }); }); } /** * Process a `'keydown'` event and invoke a matching key binding. * * @param event - The event object for a `'keydown'` event. * * #### Notes * This should be called in response to a `'keydown'` event in order * to invoke the command for the best matching key binding. * * The registry **does not** install its own listener for `'keydown'` * events. This allows the application full control over the nodes * and phase for which the registry processes `'keydown'` events. * * When the keydown event is processed, if the event target or any of its * ancestor nodes has a `data-lm-suppress-shortcuts` attribute, its keydown * events will not invoke commands. */ processKeydownEvent(event: KeyboardEvent): void { // Bail immediately if playing back keystrokes. if (this._replaying || CommandRegistry.isModifierKeyPressed(event)) { return; } // Get the normalized keystroke for the event. let keystroke = CommandRegistry.keystrokeForKeydownEvent(event); // If the keystroke is not valid for the keyboard layout, replay // any suppressed events and clear the pending state. if (!keystroke) { this._replayKeydownEvents(); this._clearPendingState(); return; } // Add the keystroke to the current key sequence. this._keystrokes.push(keystroke); // Find the exact and partial matches for the key sequence. let { exact, partial } = Private.matchKeyBinding( this._keyBindings, this._keystrokes, event ); // If there is no exact match and no partial match, replay // any suppressed events and clear the pending state. if (!exact && !partial) { this._replayKeydownEvents(); this._clearPendingState(); return; } // Stop propagation of the event. If there is only a partial match, // the event will be replayed if a final exact match never occurs. event.preventDefault(); event.stopPropagation(); // If there is an exact match but no partial match, the exact match // can be dispatched immediately. The pending state is cleared so // the next key press starts from the default state. if (exact && !partial) { this._executeKeyBinding(exact); this._clearPendingState(); return; } // If there is both an exact match and a partial match, the exact // match is stored for future dispatch in case the timer expires // before a more specific match is triggered. if (exact) { this._exactKeyMatch = exact; } // Store the event for possible playback in the future. this._keydownEvents.push(event); // (Re)start the timer to dispatch the most recent exact match // in case the partial match fails to result in an exact match. this._startTimer(); } /** * Start or restart the pending timeout. */ private _startTimer(): void { this._clearTimer(); this._timerID = window.setTimeout(() => { this._onPendingTimeout(); }, Private.CHORD_TIMEOUT); } /** * Clear the pending timeout. */ private _clearTimer(): void { if (this._timerID !== 0) { clearTimeout(this._timerID); this._timerID = 0; } } /** * Replay the keydown events which were suppressed. */ private _replayKeydownEvents(): void { if (this._keydownEvents.length === 0) { return; } this._replaying = true; this._keydownEvents.forEach(Private.replayKeyEvent); this._replaying = false; } /** * Execute the command for the given key binding. * * If the command is missing or disabled, a warning will be logged. */ private _executeKeyBinding(binding: CommandRegistry.IKeyBinding): void { let { command, args } = binding; if (!this.hasCommand(command) || !this.isEnabled(command, args)) { let word = this.hasCommand(command) ? 'enabled' : 'registered'; let keys = binding.keys.join(', '); let msg1 = `Cannot execute key binding '${keys}':`; let msg2 = `command '${command}' is not ${word}.`; console.warn(`${msg1} ${msg2}`); return; } this.execute(command, args); } /** * Clear the internal pending state. */ private _clearPendingState(): void { this._clearTimer(); this._exactKeyMatch = null; this._keystrokes.length = 0; this._keydownEvents.length = 0; } /** * Handle the partial match timeout. */ private _onPendingTimeout(): void { this._timerID = 0; if (this._exactKeyMatch) { this._executeKeyBinding(this._exactKeyMatch); } else { this._replayKeydownEvents(); } this._clearPendingState(); } private _timerID = 0; private _replaying = false; private _keystrokes: string[] = []; private _keydownEvents: KeyboardEvent[] = []; private _keyBindings: CommandRegistry.IKeyBinding[] = []; private _exactKeyMatch: CommandRegistry.IKeyBinding | null = null; private _commands: { [id: string]: Private.ICommand } = Object.create(null); private _commandChanged = new Signal< this, CommandRegistry.ICommandChangedArgs >(this); private _commandExecuted = new Signal< this, CommandRegistry.ICommandExecutedArgs >(this); private _keyBindingChanged = new Signal< this, CommandRegistry.IKeyBindingChangedArgs >(this); } /** * The namespace for the `CommandRegistry` class statics. */ export namespace CommandRegistry { /** * A type alias for a user-defined command function. */ export type CommandFunc = (args: ReadonlyPartialJSONObject) => T; /** * A type alias for a simple immutable string dataset. */ export type Dataset = { readonly [key: string]: string }; /** * An options object for creating a command. * * #### Notes * A command is an abstract representation of code to be executed along * with metadata for describing how the command should be displayed in * a visual representation. * * A command is a collection of functions, *not* methods. The command * registry will always invoke the command functions with a `thisArg` * which is `undefined`. */ export interface ICommandOptions { /** * The function to invoke when the command is executed. * * #### Notes * This should return the result of the command (if applicable) or * a promise which yields the result. The result is resolved as a * promise and that promise is returned to the code which executed * the command. * * This may be invoked even when `isEnabled` returns `false`. */ execute: CommandFunc>; /** * The label for the command. * * #### Notes * This can be a string literal, or a function which returns the * label based on the provided command arguments. * * The label is often used as the primary text for the command. * * The default value is an empty string. */ label?: string | CommandFunc; /** * The index of the mnemonic character in the command's label. * * #### Notes * This can be an index literal, or a function which returns the * mnemonic index based on the provided command arguments. * * The mnemonic character is often used by menus to provide easy * single-key keyboard access for triggering a menu item. It is * typically rendered as an underlined character in the label. * * The default value is `-1`. */ mnemonic?: number | CommandFunc; /** * The icon renderer for the command. * * #### Notes * This can be an IRenderer object, or a function which returns the * renderer based on the provided command arguments. * * The default value is undefined. * * DEPRECATED: if set to a string value, the .icon field will function as * an alias for the .iconClass field, for backwards compatibility */ icon?: | VirtualElement.IRenderer | undefined /* */ | string /* */ | CommandFunc< | VirtualElement.IRenderer | undefined /* */ | string /* */ >; /** * The icon class for the command. * * #### Notes * This class name will be added to the icon node for the visual * representation of the command. * * Multiple class names can be separated with white space. * * This can be a string literal, or a function which returns the * icon based on the provided command arguments. * * The default value is an empty string. */ iconClass?: string | CommandFunc; /** * The icon label for the command. * * #### Notes * This label will be added as text to the icon node for the visual * representation of the command. * * This can be a string literal, or a function which returns the * label based on the provided command arguments. * * The default value is an empty string. */ iconLabel?: string | CommandFunc; /** * The caption for the command. * * #### Notes * This should be a simple one line description of the command. It * is used by some visual representations to show quick info about * the command. * * This can be a string literal, or a function which returns the * caption based on the provided command arguments. * * The default value is an empty string. */ caption?: string | CommandFunc; /** * The usage text for the command. * * #### Notes * This should be a full description of the command, which includes * information about the structure of the arguments and the type of * the return value. It is used by some visual representations when * displaying complete help info about the command. * * This can be a string literal, or a function which returns the * usage text based on the provided command arguments. * * The default value is an empty string. */ usage?: string | CommandFunc; /** * The general class name for the command. * * #### Notes * This class name will be added to the primary node for the visual * representation of the command. * * Multiple class names can be separated with white space. * * This can be a string literal, or a function which returns the * class name based on the provided command arguments. * * The default value is an empty string. */ className?: string | CommandFunc; /** * The dataset for the command. * * #### Notes * The dataset values will be added to the primary node for the * visual representation of the command. * * This can be a dataset object, or a function which returns the * dataset object based on the provided command arguments. * * The default value is an empty dataset. */ dataset?: Dataset | CommandFunc; /** * A function which indicates whether the command is enabled. * * #### Notes * Visual representations may use this value to display a disabled * command as grayed-out or in some other non-interactive fashion. * * The default value is `() => true`. */ isEnabled?: CommandFunc; /** * A function which indicates whether the command is toggled. * * #### Notes * Visual representations may use this value to display a toggled * command in a different form, such as a check mark icon for a * menu item or a depressed state for a toggle button. * * The default value is `() => false`. */ isToggled?: CommandFunc; /** * A function which indicates whether the command is toggleable. * * #### Notes * Visual representations may use this value to display a toggled command in * a different form, such as a check box for a menu item or a depressed * state for a toggle button. This attribute also allows for accessible * interfaces to notify the user that the command corresponds to some state. * * The default value is `true` if an `isToggled` function is given, `false` * otherwise. */ isToggleable?: boolean; /** * A function which indicates whether the command is visible. * * #### Notes * Visual representations may use this value to hide or otherwise * not display a non-visible command. * * The default value is `() => true`. */ isVisible?: CommandFunc; } /** * An arguments object for the `commandChanged` signal. */ export interface ICommandChangedArgs { /** * The id of the associated command. * * This will be `undefined` when the type is `'many-changed'`. */ readonly id: string | undefined; /** * Whether the command was added, removed, or changed. */ readonly type: 'added' | 'removed' | 'changed' | 'many-changed'; } /** * An arguments object for the `commandExecuted` signal. */ export interface ICommandExecutedArgs { /** * The id of the associated command. */ readonly id: string; /** * The arguments object passed to the command. */ readonly args: ReadonlyPartialJSONObject; /** * The promise which resolves with the result of the command. */ readonly result: Promise; } /** * An options object for creating a key binding. */ export interface IKeyBindingOptions { /** * The default key sequence for the key binding. * * A key sequence is composed of one or more keystrokes, where each * keystroke is a combination of modifiers and a primary key. * * Most key sequences will contain a single keystroke. Key sequences * with multiple keystrokes are called "chords", and are useful for * implementing modal input (ala Vim). * * Each keystroke in the sequence should be of the form: * `[ [ [ ]]]` * * The supported modifiers are: `Accel`, `Alt`, `Cmd`, `Ctrl`, and * `Shift`. The `Accel` modifier is translated to `Cmd` on Mac and * `Ctrl` on all other platforms. The `Cmd` modifier is ignored on * non-Mac platforms. * * Keystrokes are case sensitive. * * **Examples:** `['Accel C']`, `['Shift F11']`, `['D', 'D']` */ keys: string[]; /** * The CSS selector for the key binding. * * The key binding will only be invoked when the selector matches a * node on the propagation path of the keydown event. This allows * the key binding to be restricted to user-defined contexts. * * The selector must not contain commas. */ selector: string; /** * The id of the command to execute when the binding is matched. */ command: string; /** * The arguments for the command, if necessary. * * The default value is an empty object. */ args?: ReadonlyPartialJSONObject; /** * The key sequence to use when running on Windows. * * If provided, this will override `keys` on Windows platforms. */ winKeys?: string[]; /** * The key sequence to use when running on Mac. * * If provided, this will override `keys` on Mac platforms. */ macKeys?: string[]; /** * The key sequence to use when running on Linux. * * If provided, this will override `keys` on Linux platforms. */ linuxKeys?: string[]; } /** * An object which represents a key binding. * * #### Notes * A key binding is an immutable object created by a registry. */ export interface IKeyBinding { /** * The key sequence for the binding. */ readonly keys: ReadonlyArray; /** * The CSS selector for the binding. */ readonly selector: string; /** * The command executed when the binding is matched. */ readonly command: string; /** * The arguments for the command. */ readonly args: ReadonlyPartialJSONObject; } /** * An arguments object for the `keyBindingChanged` signal. */ export interface IKeyBindingChangedArgs { /** * The key binding which was changed. */ readonly binding: IKeyBinding; /** * Whether the key binding was added or removed. */ readonly type: 'added' | 'removed'; } /** * An object which holds the results of parsing a keystroke. */ export interface IKeystrokeParts { /** * Whether `'Cmd'` appears in the keystroke. */ cmd: boolean; /** * Whether `'Ctrl'` appears in the keystroke. */ ctrl: boolean; /** * Whether `'Alt'` appears in the keystroke. */ alt: boolean; /** * Whether `'Shift'` appears in the keystroke. */ shift: boolean; /** * The primary key for the keystroke. */ key: string; } /** * Parse a keystroke into its constituent components. * * @param keystroke - The keystroke of interest. * * @returns The parsed components of the keystroke. * * #### Notes * The keystroke should be of the form: * `[ [ [ ]]]` * * The supported modifiers are: `Accel`, `Alt`, `Cmd`, `Ctrl`, and * `Shift`. The `Accel` modifier is translated to `Cmd` on Mac and * `Ctrl` on all other platforms. * * The parsing is tolerant and will not throw exceptions. Notably: * - Duplicate modifiers are ignored. * - Extra primary keys are ignored. * - The order of modifiers and primary key is irrelevant. * - The keystroke parts should be separated by whitespace. * - The keystroke is case sensitive. */ export function parseKeystroke(keystroke: string): IKeystrokeParts { let key = ''; let alt = false; let cmd = false; let ctrl = false; let shift = false; for (let token of keystroke.split(/\s+/)) { if (token === 'Accel') { if (Platform.IS_MAC) { cmd = true; } else { ctrl = true; } } else if (token === 'Alt') { alt = true; } else if (token === 'Cmd') { cmd = true; } else if (token === 'Ctrl') { ctrl = true; } else if (token === 'Shift') { shift = true; } else if (token.length > 0) { key = token; } } return { cmd, ctrl, alt, shift, key }; } /** * Normalize a keystroke into a canonical representation. * * @param keystroke - The keystroke of interest. * * @returns The normalized representation of the keystroke. * * #### Notes * This normalizes the keystroke by removing duplicate modifiers and * extra primary keys, and assembling the parts in a canonical order. * * The `Cmd` modifier is ignored on non-Mac platforms. */ export function normalizeKeystroke(keystroke: string): string { let mods = ''; let parts = parseKeystroke(keystroke); if (parts.ctrl) { mods += 'Ctrl '; } if (parts.alt) { mods += 'Alt '; } if (parts.shift) { mods += 'Shift '; } if (parts.cmd && Platform.IS_MAC) { mods += 'Cmd '; } return mods + parts.key; } /** * Get the platform-specific normalized keys for an options object. * * @param options - The options for the key binding. * * @returns Array of combined, normalized keys. */ export function normalizeKeys(options: IKeyBindingOptions): string[] { let keys: string[]; if (Platform.IS_WIN) { keys = options.winKeys || options.keys; } else if (Platform.IS_MAC) { keys = options.macKeys || options.keys; } else { keys = options.linuxKeys || options.keys; } return keys.map(normalizeKeystroke); } /** * Format a keystroke for display on the local system. */ export function formatKeystroke(keystroke: string): string { let mods = []; let separator = Platform.IS_MAC ? ' ' : '+'; let parts = parseKeystroke(keystroke); if (parts.ctrl) { mods.push('Ctrl'); } if (parts.alt) { mods.push('Alt'); } if (parts.shift) { mods.push('Shift'); } if (Platform.IS_MAC && parts.cmd) { mods.push('Cmd'); } mods.push(parts.key); return mods.map(Private.formatKey).join(separator); } /** * Check if `'keydown'` event is caused by pressing a modifier key that should be ignored. * * @param event - The event object for a `'keydown'` event. * * @returns `true` if modifier key was pressed, `false` otherwise. */ export function isModifierKeyPressed(event: KeyboardEvent): boolean { let layout = getKeyboardLayout(); let key = layout.keyForKeydownEvent(event); return layout.isModifierKey(key); } /** * Create a normalized keystroke for a `'keydown'` event. * * @param event - The event object for a `'keydown'` event. * * @returns A normalized keystroke, or an empty string if the event * does not represent a valid keystroke for the given layout. */ export function keystrokeForKeydownEvent(event: KeyboardEvent): string { let layout = getKeyboardLayout(); let key = layout.keyForKeydownEvent(event); if (!key || layout.isModifierKey(key)) { return ''; } let mods = []; if (event.ctrlKey) { mods.push('Ctrl'); } if (event.altKey) { mods.push('Alt'); } if (event.shiftKey) { mods.push('Shift'); } if (event.metaKey && Platform.IS_MAC) { mods.push('Cmd'); } mods.push(key); return mods.join(' '); } } /** * The namespace for the module implementation details. */ namespace Private { /** * The timeout in ms for triggering a key binding chord. */ export const CHORD_TIMEOUT = 1000; /** * A convenience type alias for a command func. */ export type CommandFunc = CommandRegistry.CommandFunc; /** * A convenience type alias for a command dataset. */ export type Dataset = CommandRegistry.Dataset; /** * A normalized command object. */ export interface ICommand { readonly execute: CommandFunc; readonly label: CommandFunc; readonly mnemonic: CommandFunc; readonly icon: CommandFunc< | VirtualElement.IRenderer | undefined /* */ | string /* */ >; readonly iconClass: CommandFunc; readonly iconLabel: CommandFunc; readonly caption: CommandFunc; readonly usage: CommandFunc; readonly className: CommandFunc; readonly dataset: CommandFunc; readonly isEnabled: CommandFunc; readonly isToggled: CommandFunc; readonly isToggleable: boolean; readonly isVisible: CommandFunc; } /** * Create a normalized command from an options object. */ export function createCommand( options: CommandRegistry.ICommandOptions ): ICommand { let icon; let iconClass; /* */ if (!options.icon || typeof options.icon === 'string') { // alias icon to iconClass iconClass = asFunc(options.iconClass || options.icon, emptyStringFunc); icon = iconClass; } else { /* / */ iconClass = asFunc(options.iconClass, emptyStringFunc); icon = asFunc(options.icon, undefinedFunc); /* */ } /* */ return { execute: options.execute, label: asFunc(options.label, emptyStringFunc), mnemonic: asFunc(options.mnemonic, negativeOneFunc), icon, iconClass, iconLabel: asFunc(options.iconLabel, emptyStringFunc), caption: asFunc(options.caption, emptyStringFunc), usage: asFunc(options.usage, emptyStringFunc), className: asFunc(options.className, emptyStringFunc), dataset: asFunc(options.dataset, emptyDatasetFunc), isEnabled: options.isEnabled || trueFunc, isToggled: options.isToggled || falseFunc, isToggleable: options.isToggleable || !!options.isToggled, isVisible: options.isVisible || trueFunc }; } /** * Create a key binding object from key binding options. */ export function createKeyBinding( options: CommandRegistry.IKeyBindingOptions ): CommandRegistry.IKeyBinding { return { keys: CommandRegistry.normalizeKeys(options), selector: validateSelector(options), command: options.command, args: options.args || JSONExt.emptyObject }; } /** * An object which holds the results of a key binding match. */ export interface IMatchResult { /** * The best key binding which exactly matches the key sequence. */ exact: CommandRegistry.IKeyBinding | null; /** * Whether there are bindings which partially match the sequence. */ partial: boolean; } /** * Find the key bindings which match a key sequence. * * This returns a match result which contains the best exact matching * binding, and a flag which indicates if there are partial matches. */ export function matchKeyBinding( bindings: ReadonlyArray, keys: ReadonlyArray, event: KeyboardEvent ): IMatchResult { // The current best exact match. let exact: CommandRegistry.IKeyBinding | null = null; // Whether a partial match has been found. let partial = false; // The match distance for the exact match. let distance = Infinity; // The specificity for the exact match. let specificity = 0; // Iterate over the bindings and search for the best match. for (let i = 0, n = bindings.length; i < n; ++i) { // Lookup the current binding. let binding = bindings[i]; // Check whether the key binding sequence is a match. let sqm = matchSequence(binding.keys, keys); // If there is no match, the binding is ignored. if (sqm === SequenceMatch.None) { continue; } // If it is a partial match and no other partial match has been // found, ensure the selector matches and set the partial flag. if (sqm === SequenceMatch.Partial) { if (!partial && targetDistance(binding.selector, event) !== -1) { partial = true; } continue; } // Ignore the match if the selector doesn't match, or if the // matched node is farther away than the current best match. let td = targetDistance(binding.selector, event); if (td === -1 || td > distance) { continue; } // Get the specificity for the selector. let sp = Selector.calculateSpecificity(binding.selector); // Update the best match if this match is stronger. if (!exact || td < distance || sp >= specificity) { exact = binding; distance = td; specificity = sp; } } // Return the match result. return { exact, partial }; } /** * Replay a keyboard event. * * This synthetically dispatches a clone of the keyboard event. */ export function replayKeyEvent(event: KeyboardEvent): void { event.target!.dispatchEvent(cloneKeyboardEvent(event)); } export function formatKey(key: string): string { if (Platform.IS_MAC) { return MAC_DISPLAY.hasOwnProperty(key) ? MAC_DISPLAY[key] : key; } else { return WIN_DISPLAY.hasOwnProperty(key) ? WIN_DISPLAY[key] : key; } } const MAC_DISPLAY: { [key: string]: string } = { Backspace: '⌫', Tab: '⇥', Enter: '↩', Shift: '⇧', Ctrl: '⌃', Alt: '⌥', Escape: '⎋', PageUp: '⇞', PageDown: '⇟', End: '↘', Home: '↖', ArrowLeft: 'â†', ArrowUp: '↑', ArrowRight: '→', ArrowDown: '↓', Delete: '⌦', Cmd: '⌘' }; const WIN_DISPLAY: { [key: string]: string } = { Escape: 'Esc', PageUp: 'Page Up', PageDown: 'Page Down', ArrowLeft: 'Left', ArrowUp: 'Up', ArrowRight: 'Right', ArrowDown: 'Down', Delete: 'Del' }; /** * A singleton empty string function. */ const emptyStringFunc = () => ''; /** * A singleton `-1` number function */ const negativeOneFunc = () => -1; /** * A singleton true boolean function. */ const trueFunc = () => true; /** * A singleton false boolean function. */ const falseFunc = () => false; /** * A singleton empty dataset function. */ const emptyDatasetFunc = () => ({}); /** * A singleton undefined function */ const undefinedFunc = () => undefined; /** * Cast a value or command func to a command func. */ function asFunc( value: undefined | T | CommandFunc, dfault: CommandFunc ): CommandFunc { if (value === undefined) { return dfault; } if (typeof value === 'function') { return value as CommandFunc; } return () => value; } /** * Validate the selector for an options object. * * This returns the validated selector, or throws if the selector is * invalid or contains commas. */ function validateSelector( options: CommandRegistry.IKeyBindingOptions ): string { if (options.selector.indexOf(',') !== -1) { throw new Error(`Selector cannot contain commas: ${options.selector}`); } if (!Selector.isValid(options.selector)) { throw new Error(`Invalid selector: ${options.selector}`); } return options.selector; } /** * An enum which describes the possible sequence matches. */ const enum SequenceMatch { None, Exact, Partial } /** * Test whether a key binding sequence matches a key sequence. * * Returns a `SequenceMatch` value indicating the type of match. */ function matchSequence( bindKeys: ReadonlyArray, userKeys: ReadonlyArray ): SequenceMatch { if (bindKeys.length < userKeys.length) { return SequenceMatch.None; } for (let i = 0, n = userKeys.length; i < n; ++i) { if (bindKeys[i] !== userKeys[i]) { return SequenceMatch.None; } } if (bindKeys.length > userKeys.length) { return SequenceMatch.Partial; } return SequenceMatch.Exact; } /** * Find the distance from the target node to the first matching node. * * This traverses the event path from `target` to `currentTarget` and * computes the distance from `target` to the first node which matches * the CSS selector. If no match is found, `-1` is returned. */ function targetDistance(selector: string, event: KeyboardEvent): number { let targ = event.target as Element | null; let curr = event.currentTarget as Element | null; for (let dist = 0; targ !== null; targ = targ.parentElement, ++dist) { if (targ.hasAttribute('data-lm-suppress-shortcuts')) { return -1; } /* */ if (targ.hasAttribute('data-p-suppress-shortcuts')) { return -1; } /* */ if (Selector.matches(targ, selector)) { return dist; } if (targ === curr) { return -1; } } return -1; } /** * Clone a keyboard event. */ function cloneKeyboardEvent(event: KeyboardEvent): KeyboardEvent { // A custom event is required because Chrome nulls out the // `keyCode` field in user-generated `KeyboardEvent` types. let clone = document.createEvent('Event') as any; let bubbles = event.bubbles || true; let cancelable = event.cancelable || true; clone.initEvent(event.type || 'keydown', bubbles, cancelable); clone.key = event.key || ''; clone.keyCode = event.keyCode || 0; clone.which = event.keyCode || 0; clone.ctrlKey = event.ctrlKey || false; clone.altKey = event.altKey || false; clone.shiftKey = event.shiftKey || false; clone.metaKey = event.metaKey || false; clone.view = event.view || window; return clone as KeyboardEvent; } } lumino-2021.12.13/packages/commands/tdoptions.json000066400000000000000000000005171415564225700217210ustar00rootroot00000000000000{ "excludeNotExported": true, "mode": "file", "target": "es5", "module": "es5", "lib": [ "lib.es2015.d.ts", "lib.es2015.collection.d.ts", "lib.es2015.promise.d.ts", "lib.dom.d.ts" ], "out": "../../docs/source/api/commands", "baseUrl": ".", "paths": { "@lumino/*": ["node_modules/@lumino/*"] } } lumino-2021.12.13/packages/commands/tests/000077500000000000000000000000001415564225700201425ustar00rootroot00000000000000lumino-2021.12.13/packages/commands/tests/karma.conf.js000066400000000000000000000005051415564225700225170ustar00rootroot00000000000000module.exports = function (config) { config.set({ basePath: '.', frameworks: ['mocha'], reporters: ['mocha'], files: ['build/bundle.test.js'], port: 9876, colors: true, singleRun: true, browserNoActivityTimeout: 30000, failOnEmptyTestSuite: false, logLevel: config.LOG_INFO }); }; lumino-2021.12.13/packages/commands/tests/src/000077500000000000000000000000001415564225700207315ustar00rootroot00000000000000lumino-2021.12.13/packages/commands/tests/src/index.spec.ts000066400000000000000000001113601415564225700233430ustar00rootroot00000000000000/* eslint-disable @typescript-eslint/no-empty-function */ // Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import 'es6-promise/auto'; // polyfill Promise on IE import { expect } from 'chai'; import { generate } from 'simulate-event'; import { CommandRegistry } from '@lumino/commands'; import { JSONObject } from '@lumino/coreutils'; import { Platform } from '@lumino/domutils'; const NULL_COMMAND = { execute: (args: JSONObject) => { return args; } }; describe('@lumino/commands', () => { describe('CommandRegistry', () => { let registry: CommandRegistry = null!; beforeEach(() => { registry = new CommandRegistry(); }); describe('#constructor()', () => { it('should take no arguments', () => { expect(registry).to.be.an.instanceof(CommandRegistry); }); }); describe('#commandChanged', () => { it('should be emitted when a command is added', () => { let called = false; registry.commandChanged.connect((reg, args) => { expect(reg).to.equal(registry); expect(args.id).to.equal('test'); expect(args.type).to.equal('added'); called = true; }); registry.addCommand('test', NULL_COMMAND); expect(called).to.equal(true); }); it('should be emitted when a command is changed', () => { let called = false; registry.addCommand('test', NULL_COMMAND); registry.commandChanged.connect((reg, args) => { expect(reg).to.equal(registry); expect(args.id).to.equal('test'); expect(args.type).to.equal('changed'); called = true; }); registry.notifyCommandChanged('test'); expect(called).to.equal(true); }); it('should be emitted when a command is removed', () => { let called = false; let disposable = registry.addCommand('test', NULL_COMMAND); registry.commandChanged.connect((reg, args) => { expect(reg).to.equal(registry); expect(args.id).to.equal('test'); expect(args.type).to.equal('removed'); called = true; }); disposable.dispose(); expect(called).to.equal(true); }); }); describe('#commandExecuted', () => { it('should be emitted when a command is executed', () => { let called = false; registry.addCommand('test', NULL_COMMAND); registry.commandExecuted.connect((reg, args) => { expect(reg).to.equal(registry); expect(args.id).to.equal('test'); expect(args.args).to.deep.equal({}); expect(args.result).to.be.an.instanceof(Promise); called = true; }); registry.execute('test'); expect(called).to.equal(true); }); }); describe('#keyBindings', () => { it('should be the keybindings in the palette', () => { registry.addCommand('test', { execute: () => {} }); registry.addKeyBinding({ keys: ['Ctrl ;'], selector: `body`, command: 'test' }); expect(registry.keyBindings.length).to.equal(1); expect(registry.keyBindings[0].command).to.equal('test'); }); }); describe('#listCommands()', () => { it('should list the ids of the registered commands', () => { registry.addCommand('test0', NULL_COMMAND); registry.addCommand('test1', NULL_COMMAND); expect(registry.listCommands()).to.deep.equal(['test0', 'test1']); }); it('should be a new array', () => { registry.addCommand('test0', NULL_COMMAND); registry.addCommand('test1', NULL_COMMAND); let cmds = registry.listCommands(); cmds.push('test2'); expect(registry.listCommands()).to.deep.equal(['test0', 'test1']); }); }); describe('#hasCommand()', () => { it('should test whether a specific command is registerd', () => { registry.addCommand('test', NULL_COMMAND); expect(registry.hasCommand('test')).to.equal(true); expect(registry.hasCommand('foo')).to.equal(false); }); }); describe('#addCommand()', () => { it('should add a command to the registry', () => { registry.addCommand('test', NULL_COMMAND); expect(registry.hasCommand('test')).to.equal(true); }); it('should return a disposable which will unregister the command', () => { let disposable = registry.addCommand('test', NULL_COMMAND); disposable.dispose(); expect(registry.hasCommand('test')).to.equal(false); }); it('should throw an error if the given `id` is already registered', () => { registry.addCommand('test', NULL_COMMAND); expect(() => { registry.addCommand('test', NULL_COMMAND); }).to.throw(Error); }); it('should clone the `cmd` before adding it to the registry', () => { let cmd = { execute: (args: JSONObject) => { return args; }, label: 'foo' }; registry.addCommand('test', cmd); cmd.label = 'bar'; expect(registry.label('test')).to.equal('foo'); }); }); describe('#notifyCommandChanged()', () => { it('should emit the `commandChanged` signal for the command', () => { let called = false; registry.addCommand('test', NULL_COMMAND); registry.commandChanged.connect((reg, args) => { expect(reg).to.equal(registry); expect(args.id).to.equal('test'); expect(args.type).to.equal('changed'); called = true; }); registry.notifyCommandChanged('test'); expect(called).to.equal(true); }); it('should throw an error if the command is not registered', () => { expect(() => { registry.notifyCommandChanged('foo'); }).to.throw(Error); }); }); describe('#label()', () => { it('should get the display label for a specific command', () => { let cmd = { execute: (args: JSONObject) => { return args; }, label: 'foo' }; registry.addCommand('test', cmd); expect(registry.label('test')).to.equal('foo'); }); it('should give the appropriate label given arguments', () => { let cmd = { execute: (args: JSONObject) => { return args; }, label: (args: JSONObject) => { return JSON.stringify(args); } }; registry.addCommand('test', cmd); expect(registry.label('test', {})).to.equal('{}'); }); it('should return an empty string if the command is not registered', () => { expect(registry.label('foo')).to.equal(''); }); it('should default to an empty string for a command', () => { registry.addCommand('test', NULL_COMMAND); expect(registry.label('test')).to.equal(''); }); }); describe('#mnemonic()', () => { it('should get the mnemonic index for a specific command', () => { let cmd = { execute: (args: JSONObject) => { return args; }, mnemonic: 1 }; registry.addCommand('test', cmd); expect(registry.mnemonic('test')).to.equal(1); }); it('should give the appropriate mnemonic given arguments', () => { let cmd = { execute: (args: JSONObject) => { return args; }, mnemonic: (args: JSONObject) => { return JSON.stringify(args).length; } }; registry.addCommand('test', cmd); expect(registry.mnemonic('test', {})).to.equal(2); }); it('should return a `-1` if the command is not registered', () => { expect(registry.mnemonic('foo')).to.equal(-1); }); it('should default to `-1` for a command', () => { registry.addCommand('test', NULL_COMMAND); expect(registry.mnemonic('test')).to.equal(-1); }); }); describe('#icon()', () => { const iconRenderer = { render: (host: HTMLElement, options?: any) => { const renderNode = document.createElement('div'); renderNode.className = 'p-render'; host.appendChild(renderNode); } }; it('should get the icon for a specific command', () => { let cmd = { execute: (args: JSONObject) => { return args; }, icon: iconRenderer }; registry.addCommand('test', cmd); expect(registry.icon('test')).to.equal(iconRenderer); }); it('should give the appropriate icon given arguments', () => { let cmd = { execute: (args: JSONObject) => { return args; }, icon: (args: JSONObject) => { return JSON.stringify(args); } }; registry.addCommand('test', cmd); expect(registry.icon('test', {})).to.equal('{}'); }); it('should return an empty string if the command is not registered', () => { expect(registry.icon('foo')).to.equal(''); }); it('should default to an empty string for a command', () => { registry.addCommand('test', NULL_COMMAND); expect(registry.icon('test')).to.equal(''); }); /* */ it('should be able to return a string value', () => { let cmd = { execute: (args: JSONObject) => { return args; }, icon: 'foo' }; registry.addCommand('test', cmd); expect(registry.icon('test')).to.equal('foo'); }); it('should alias .iconClass() if cmd.icon is unset', () => { let cmd = { execute: (args: JSONObject) => { return args; }, iconClass: 'foo' }; registry.addCommand('test', cmd); expect(registry.icon('test')).to.equal('foo'); }); /* */ }); describe('#caption()', () => { it('should get the caption for a specific command', () => { let cmd = { execute: (args: JSONObject) => { return args; }, caption: 'foo' }; registry.addCommand('test', cmd); expect(registry.caption('test')).to.equal('foo'); }); it('should give the appropriate caption given arguments', () => { let cmd = { execute: (args: JSONObject) => { return args; }, caption: (args: JSONObject) => { return JSON.stringify(args); } }; registry.addCommand('test', cmd); expect(registry.caption('test', {})).to.equal('{}'); }); it('should return an empty string if the command is not registered', () => { expect(registry.caption('foo')).to.equal(''); }); it('should default to an empty string for a command', () => { registry.addCommand('test', NULL_COMMAND); expect(registry.caption('test')).to.equal(''); }); }); describe('#usage()', () => { it('should get the usage text for a specific command', () => { let cmd = { execute: (args: JSONObject) => { return args; }, usage: 'foo' }; registry.addCommand('test', cmd); expect(registry.usage('test')).to.equal('foo'); }); it('should give the appropriate usage text given arguments', () => { let cmd = { execute: (args: JSONObject) => { return args; }, usage: (args: JSONObject) => { return JSON.stringify(args); } }; registry.addCommand('test', cmd); expect(registry.usage('test', {})).to.equal('{}'); }); it('should return an empty string if the command is not registered', () => { expect(registry.usage('foo')).to.equal(''); }); it('should default to an empty string for a command', () => { registry.addCommand('test', NULL_COMMAND); expect(registry.usage('test')).to.equal(''); }); }); describe('#className()', () => { it('should get the extra class name for a specific command', () => { let cmd = { execute: (args: JSONObject) => { return args; }, className: 'foo' }; registry.addCommand('test', cmd); expect(registry.className('test')).to.equal('foo'); }); it('should give the appropriate class name given arguments', () => { let cmd = { execute: (args: JSONObject) => { return args; }, className: (args: JSONObject) => { return JSON.stringify(args); } }; registry.addCommand('test', cmd); expect(registry.className('test', {})).to.equal('{}'); }); it('should return an empty string if the command is not registered', () => { expect(registry.className('foo')).to.equal(''); }); it('should default to an empty string for a command', () => { registry.addCommand('test', NULL_COMMAND); expect(registry.className('test')).to.equal(''); }); }); describe('#isEnabled()', () => { it('should test whether a specific command is enabled', () => { let cmd = { execute: (args: JSONObject) => { return args; }, isEnabled: (args: JSONObject) => { return args.enabled as boolean; } }; registry.addCommand('test', cmd); expect(registry.isEnabled('test', { enabled: true })).to.equal(true); expect(registry.isEnabled('test', { enabled: false })).to.equal(false); }); it('should return `false` if the command is not registered', () => { expect(registry.isEnabled('foo')).to.equal(false); }); it('should default to `true` for a command', () => { registry.addCommand('test', NULL_COMMAND); expect(registry.isEnabled('test')).to.equal(true); }); }); describe('#isToggled()', () => { it('should test whether a specific command is toggled', () => { let cmd = { execute: (args: JSONObject) => { return args; }, isToggled: (args: JSONObject) => { return args.toggled as boolean; } }; registry.addCommand('test', cmd); expect(registry.isToggled('test', { toggled: true })).to.equal(true); expect(registry.isToggled('test', { toggled: false })).to.equal(false); }); it('should return `false` if the command is not registered', () => { expect(registry.isToggled('foo')).to.equal(false); }); it('should default to `false` for a command', () => { registry.addCommand('test', NULL_COMMAND); expect(registry.isToggled('test')).to.equal(false); }); }); describe('#isVisible()', () => { it('should test whether a specific command is visible', () => { let cmd = { execute: (args: JSONObject) => { return args; }, isVisible: (args: JSONObject) => { return args.visible as boolean; } }; registry.addCommand('test', cmd); expect(registry.isVisible('test', { visible: true })).to.equal(true); expect(registry.isVisible('test', { visible: false })).to.equal(false); }); it('should return `false` if the command is not registered', () => { expect(registry.isVisible('foo')).to.equal(false); }); it('should default to `true` for a command', () => { registry.addCommand('test', NULL_COMMAND); expect(registry.isVisible('test')).to.equal(true); }); }); describe('#execute()', () => { it('should execute a specific command', () => { let called = false; let cmd = { execute: (args: JSONObject) => { called = true; } }; registry.addCommand('test', cmd); registry.execute('test'); expect(called).to.equal(true); }); it('should resolve with the result of the command', done => { let cmd = { execute: (args: JSONObject) => { return args; } }; registry.addCommand('test', cmd); registry.execute('test', { foo: 12 }).then(result => { expect(result).to.deep.equal({ foo: 12 }); done(); }); }); it('should reject if the command throws an error', done => { let cmd = { execute: (args: JSONObject) => { throw new Error(''); } }; registry.addCommand('test', cmd); registry.execute('test').catch(() => { done(); }); }); it('should reject if the command is not registered', done => { registry.execute('foo').catch(() => { done(); }); }); }); let elemID = 0; let elem: HTMLElement = null!; let parent: HTMLElement = null!; beforeEach(() => { parent = document.createElement('div') as HTMLElement; elem = document.createElement('div') as HTMLElement; parent.classList.add('lm-test-parent'); elem.id = `test${elemID++}`; elem.addEventListener('keydown', event => { registry.processKeydownEvent(event); }); parent.appendChild(elem); document.body.appendChild(parent); }); afterEach(() => { document.body.removeChild(parent); }); describe('#addKeyBinding()', () => { it('should add key bindings to the registry', () => { let called = false; registry.addCommand('test', { execute: () => { called = true; } }); registry.addKeyBinding({ keys: ['Ctrl ;'], selector: `#${elem.id}`, command: 'test' }); let event = generate('keydown', { keyCode: 59, ctrlKey: true }); elem.dispatchEvent(event); expect(called).to.equal(true); }); it('should remove a binding when disposed', () => { let called = false; registry.addCommand('test', { execute: () => { called = true; } }); let binding = registry.addKeyBinding({ keys: ['Ctrl ;'], selector: `#${elem.id}`, command: 'test' }); binding.dispose(); let event = generate('keydown', { keyCode: 59, ctrlKey: true }); elem.dispatchEvent(event); expect(called).to.equal(false); }); it('should emit a key binding changed signal when added and removed', () => { let added = false; registry.addCommand('test', { execute: () => {} }); registry.keyBindingChanged.connect((sender, args) => { added = args.type === 'added'; }); let binding = registry.addKeyBinding({ keys: ['Ctrl ;'], selector: `#${elem.id}`, command: 'test' }); expect(added).to.equal(true); binding.dispose(); expect(added).to.equal(false); }); it('should throw an error if binding has an invalid selector', () => { let options = { keys: ['Ctrl ;'], selector: '..', command: 'test' }; expect(() => { registry.addKeyBinding(options); }).to.throw(Error); }); }); describe('#processKeydownEvent()', () => { it('should dispatch on a correct keyboard event', () => { let called = false; registry.addCommand('test', { execute: () => { called = true; } }); registry.addKeyBinding({ keys: ['Ctrl ;'], selector: `#${elem.id}`, command: 'test' }); let event = generate('keydown', { keyCode: 59, ctrlKey: true }); elem.dispatchEvent(event); expect(called).to.equal(true); }); it('should not dispatch on a suppressed node', () => { let called = false; registry.addCommand('test', { execute: () => { called = true; } }); registry.addKeyBinding({ keys: ['Ctrl ;'], selector: `.lm-test-parent`, command: 'test' }); parent.setAttribute('data-lm-suppress-shortcuts', 'true'); let event = generate('keydown', { keyCode: 59, ctrlKey: true }); elem.dispatchEvent(event); expect(called).to.equal(false); }); it('should not dispatch on a non-matching keyboard event', () => { let called = false; registry.addCommand('test', { execute: () => { called = true; } }); registry.addKeyBinding({ keys: ['Ctrl ;'], selector: `#${elem.id}`, command: 'test' }); let event = generate('keydown', { keyCode: 45, ctrlKey: true }); elem.dispatchEvent(event); expect(called).to.equal(false); }); it('should not dispatch with non-matching modifiers', () => { let count = 0; registry.addCommand('test', { execute: () => { count++; } }); registry.addKeyBinding({ keys: ['Ctrl S'], selector: `#${elem.id}`, command: 'test' }); let eventAlt = generate('keydown', { keyCode: 83, altKey: true }); let eventShift = generate('keydown', { keyCode: 83, shiftKey: true }); elem.dispatchEvent(eventAlt); expect(count).to.equal(0); elem.dispatchEvent(eventShift); expect(count).to.equal(0); }); it('should dispatch with multiple chords in a key sequence', () => { let count = 0; registry.addCommand('test', { execute: () => { count++; } }); registry.addKeyBinding({ keys: ['Ctrl K', 'Ctrl L'], selector: `#${elem.id}`, command: 'test' }); let eventK = generate('keydown', { keyCode: 75, ctrlKey: true }); let eventL = generate('keydown', { keyCode: 76, ctrlKey: true }); elem.dispatchEvent(eventK); expect(count).to.equal(0); elem.dispatchEvent(eventL); expect(count).to.equal(1); elem.dispatchEvent(generate('keydown', eventL)); // Don't reuse; clone. expect(count).to.equal(1); elem.dispatchEvent(generate('keydown', eventK)); // Don't reuse; clone. expect(count).to.equal(1); elem.dispatchEvent(generate('keydown', eventL)); // Don't reuse; clone. expect(count).to.equal(2); }); it('should not execute handler without matching selector', () => { let count = 0; registry.addCommand('test', { execute: () => { count++; } }); registry.addKeyBinding({ keys: ['Shift P'], selector: '.inaccessible-scope', command: 'test' }); let event = generate('keydown', { keyCode: 80, shiftKey: true }); expect(count).to.equal(0); elem.dispatchEvent(event); expect(count).to.equal(0); }); it('should not execute a handler when missing a modifier', () => { let count = 0; registry.addCommand('test', { execute: () => { count++; } }); registry.addKeyBinding({ keys: ['Ctrl P'], selector: `#${elem.id}`, command: 'test' }); let event = generate('keydown', { keyCode: 17 }); expect(count).to.equal(0); elem.dispatchEvent(event); expect(count).to.equal(0); }); it('should register partial and exact matches', () => { let count1 = 0; let count2 = 0; registry.addCommand('test1', { execute: () => { count1++; } }); registry.addCommand('test2', { execute: () => { count2++; } }); registry.addKeyBinding({ keys: ['Ctrl S'], selector: `#${elem.id}`, command: 'test1' }); registry.addKeyBinding({ keys: ['Ctrl S', 'Ctrl D'], selector: `#${elem.id}`, command: 'test2' }); let event1 = generate('keydown', { keyCode: 83, ctrlKey: true }); let event2 = generate('keydown', { keyCode: 68, ctrlKey: true }); expect(count1).to.equal(0); expect(count2).to.equal(0); elem.dispatchEvent(event1); expect(count1).to.equal(0); expect(count2).to.equal(0); elem.dispatchEvent(event2); expect(count1).to.equal(0); expect(count2).to.equal(1); }); it('should recognize permutations of modifiers', () => { let count1 = 0; let count2 = 0; registry.addCommand('test1', { execute: () => { count1++; } }); registry.addCommand('test2', { execute: () => { count2++; } }); registry.addKeyBinding({ keys: ['Shift Alt Ctrl T'], selector: `#${elem.id}`, command: 'test1' }); registry.addKeyBinding({ keys: ['Alt Shift Ctrl Q'], selector: `#${elem.id}`, command: 'test2' }); let event1 = generate('keydown', { keyCode: 84, ctrlKey: true, altKey: true, shiftKey: true }); let event2 = generate('keydown', { keyCode: 81, ctrlKey: true, altKey: true, shiftKey: true }); expect(count1).to.equal(0); elem.dispatchEvent(event1); expect(count1).to.equal(1); expect(count2).to.equal(0); elem.dispatchEvent(event2); expect(count2).to.equal(1); }); it('should play back a partial match that was not completed', () => { let codes: number[] = []; let keydown = (event: KeyboardEvent) => { codes.push(event.keyCode); }; document.body.addEventListener('keydown', keydown); let called = false; registry.addCommand('test', { execute: () => { called = true; } }); registry.addKeyBinding({ keys: ['D', 'D'], selector: `#${elem.id}`, command: 'test' }); let event1 = generate('keydown', { keyCode: 68 }); let event2 = generate('keydown', { keyCode: 69 }); elem.dispatchEvent(event1); expect(codes.length).to.equal(0); elem.dispatchEvent(event2); expect(called).to.equal(false); expect(codes).to.deep.equal([68, 69]); document.body.removeEventListener('keydown', keydown); }); it('should play back a partial match that times out', done => { let codes: number[] = []; let keydown = (event: KeyboardEvent) => { codes.push(event.keyCode); }; document.body.addEventListener('keydown', keydown); let called = false; registry.addCommand('test', { execute: () => { called = true; } }); registry.addKeyBinding({ keys: ['D', 'D'], selector: `#${elem.id}`, command: 'test' }); let event = generate('keydown', { keyCode: 68 }); elem.dispatchEvent(event); expect(codes.length).to.equal(0); setTimeout(() => { expect(codes).to.deep.equal([68]); expect(called).to.equal(false); document.body.removeEventListener('keydown', keydown); done(); }, 1300); }); it('should resolve an exact match of partial match time out', done => { let called1 = false; let called2 = false; registry.addCommand('test1', { execute: () => { called1 = true; } }); registry.addCommand('test2', { execute: () => { called2 = true; } }); registry.addKeyBinding({ keys: ['D', 'D'], selector: `#${elem.id}`, command: 'test1' }); registry.addKeyBinding({ keys: ['D'], selector: `#${elem.id}`, command: 'test2' }); let event = generate('keydown', { keyCode: 68 }); elem.dispatchEvent(event); expect(called1).to.equal(false); expect(called2).to.equal(false); setTimeout(() => { expect(called1).to.equal(false); expect(called2).to.equal(true); done(); }, 1300); }); it('should pick the selector with greater specificity', () => { elem.classList.add('test'); let called1 = false; let called2 = false; registry.addCommand('test1', { execute: () => { called1 = true; } }); registry.addCommand('test2', { execute: () => { called2 = true; } }); registry.addKeyBinding({ keys: ['Ctrl ;'], selector: '.test', command: 'test1' }); registry.addKeyBinding({ keys: ['Ctrl ;'], selector: `#${elem.id}`, command: 'test2' }); let event = generate('keydown', { keyCode: 59, ctrlKey: true }); elem.dispatchEvent(event); expect(called1).to.equal(false); expect(called2).to.equal(true); }); it('should propagate if partial binding selector does not match', () => { let codes: number[] = []; let keydown = (event: KeyboardEvent) => { codes.push(event.keyCode); }; document.body.addEventListener('keydown', keydown); let called = false; registry.addCommand('test', { execute: () => { called = true; } }); registry.addKeyBinding({ keys: ['D', 'D'], selector: '#baz', command: 'test' }); let event = generate('keydown', { keyCode: 68 }); elem.dispatchEvent(event); expect(codes).to.deep.equal([68]); expect(called).to.equal(false); document.body.removeEventListener('keydown', keydown); }); it('should propagate if exact binding selector does not match', () => { let codes: number[] = []; let keydown = (event: KeyboardEvent) => { codes.push(event.keyCode); }; document.body.addEventListener('keydown', keydown); let called = false; registry.addCommand('test', { execute: () => { called = true; } }); registry.addKeyBinding({ keys: ['D'], selector: '#baz', command: 'test' }); let event = generate('keydown', { keyCode: 68 }); elem.dispatchEvent(event); expect(codes).to.deep.equal([68]); expect(called).to.equal(false); document.body.removeEventListener('keydown', keydown); }); it('should ignore modifier keys pressed in the middle of key sequence', () => { let count = 0; registry.addCommand('test', { execute: () => { count++; } }); registry.addKeyBinding({ keys: ['Ctrl K', 'Ctrl L'], selector: `#${elem.id}`, command: 'test' }); let eventK = generate('keydown', { keyCode: 75, ctrlKey: true }); let eventCtrl = generate('keydown', { keyCode: 17, ctrlKey: true }); let eventL = generate('keydown', { keyCode: 76, ctrlKey: true }); elem.dispatchEvent(eventK); expect(count).to.equal(0); elem.dispatchEvent(eventCtrl); // user presses Ctrl again - this should not break the sequence expect(count).to.equal(0); elem.dispatchEvent(eventL); expect(count).to.equal(1); }); it('should process key sequences that use different modifier keys', () => { let count = 0; registry.addCommand('test', { execute: () => { count++; } }); registry.addKeyBinding({ keys: ['Shift K', 'Ctrl L'], selector: `#${elem.id}`, command: 'test' }); let eventShift = generate('keydown', { keyCode: 16, shiftlKey: true }); let eventK = generate('keydown', { keyCode: 75, shiftKey: true }); let eventCtrl = generate('keydown', { keyCode: 17, ctrlKey: true }); let eventL = generate('keydown', { keyCode: 76, ctrlKey: true }); elem.dispatchEvent(eventShift); expect(count).to.equal(0); elem.dispatchEvent(eventK); expect(count).to.equal(0); elem.dispatchEvent(eventCtrl); expect(count).to.equal(0); elem.dispatchEvent(eventL); expect(count).to.equal(1); }); }); describe('.parseKeystroke()', () => { it('should parse a keystroke into its parts', () => { let parts = CommandRegistry.parseKeystroke('Ctrl Shift Alt S'); expect(parts.cmd).to.equal(false); expect(parts.ctrl).to.equal(true); expect(parts.alt).to.equal(true); expect(parts.shift).to.equal(true); expect(parts.key).to.equal('S'); }); it('should preserve arrow names in key without formatting', () => { let parts = CommandRegistry.parseKeystroke('ArrowRight'); expect(parts.key).to.equal('ArrowRight'); }); it('should be a tolerant parse', () => { let parts = CommandRegistry.parseKeystroke('G Ctrl Shift S Shift K'); expect(parts.cmd).to.equal(false); expect(parts.ctrl).to.equal(true); expect(parts.alt).to.equal(false); expect(parts.shift).to.equal(true); expect(parts.key).to.equal('K'); }); }); describe('.formatKeystroke()', () => { it('should prepend modifiers', () => { let label = CommandRegistry.formatKeystroke('Ctrl Alt Shift S'); if (Platform.IS_MAC) { expect(label).to.equal('\u2303 \u2325 \u21E7 S'); } else { expect(label).to.equal('Ctrl+Alt+Shift+S'); } }); it('should format arrow', () => { let label = CommandRegistry.formatKeystroke('Alt ArrowDown'); if (Platform.IS_MAC) { expect(label).to.equal('\u2325 \u2193'); } else { expect(label).to.equal('Alt+Down'); } }); }); describe('.normalizeKeystroke()', () => { it('should normalize and validate a keystroke', () => { let stroke = CommandRegistry.normalizeKeystroke('Ctrl S'); expect(stroke).to.equal('Ctrl S'); }); it('should handle multiple modifiers', () => { let stroke = CommandRegistry.normalizeKeystroke('Ctrl Shift Alt S'); expect(stroke).to.equal('Ctrl Alt Shift S'); }); it('should handle platform specific modifiers', () => { let stroke = ''; if (Platform.IS_MAC) { stroke = CommandRegistry.normalizeKeystroke('Cmd S'); expect(stroke).to.equal('Cmd S'); stroke = CommandRegistry.normalizeKeystroke('Accel S'); expect(stroke).to.equal('Cmd S'); } else { stroke = CommandRegistry.normalizeKeystroke('Cmd S'); expect(stroke).to.equal('S'); stroke = CommandRegistry.normalizeKeystroke('Accel S'); expect(stroke).to.equal('Ctrl S'); } }); }); describe('.keystrokeForKeydownEvent()', () => { it('should create a normalized keystroke', () => { let event = generate('keydown', { ctrlKey: true, keyCode: 83 }); let keystroke = CommandRegistry.keystrokeForKeydownEvent( event as KeyboardEvent ); expect(keystroke).to.equal('Ctrl S'); }); it('should handle multiple modifiers', () => { let event = generate('keydown', { ctrlKey: true, altKey: true, shiftKey: true, keyCode: 83 }); let keystroke = CommandRegistry.keystrokeForKeydownEvent( event as KeyboardEvent ); expect(keystroke).to.equal('Ctrl Alt Shift S'); }); it('should fail on an invalid shortcut', () => { let event = generate('keydown', { keyCode: -1 }); let keystroke = CommandRegistry.keystrokeForKeydownEvent( event as KeyboardEvent ); expect(keystroke).to.equal(''); }); it('should return nothing for keys that are marked as modifier in keyboard layout', () => { let event = generate('keydown', { keyCode: 17, ctrlKey: true }); let keystroke = CommandRegistry.keystrokeForKeydownEvent( event as KeyboardEvent ); expect(keystroke).to.equal(''); }); }); }); }); lumino-2021.12.13/packages/commands/tests/tsconfig.json000066400000000000000000000012771415564225700226600ustar00rootroot00000000000000{ "compilerOptions": { "declaration": false, "noImplicitAny": true, "noEmitOnError": true, "noUnusedLocals": true, "strictNullChecks": true, "module": "commonjs", "moduleResolution": "node", "target": "ES5", "outDir": "build", "lib": ["ES5", "ES2015.Promise", "ES2015.Iterable", "DOM"], "types": ["chai", "mocha"], "rootDir": "src" }, "include": ["src/*"], "references": [ { "path": "../../algorithm" }, { "path": "../../coreutils" }, { "path": "../../disposable" }, { "path": "../../domutils" }, { "path": "../../keyboard" }, { "path": "../../signaling" } ] } lumino-2021.12.13/packages/commands/tests/webpack.config.js000066400000000000000000000003061415564225700233570ustar00rootroot00000000000000var path = require('path'); module.exports = { entry: './build/index.spec.js', mode: 'development', output: { filename: './build/bundle.test.js', path: path.resolve(__dirname) } }; lumino-2021.12.13/packages/commands/tsconfig.json000066400000000000000000000015071415564225700215120ustar00rootroot00000000000000{ "compilerOptions": { "composite": true, "sourceMap": true, "declaration": true, "declarationDir": "./types", "declarationMap": true, "noImplicitAny": true, "noEmitOnError": true, "noUnusedLocals": true, "strictNullChecks": true, "module": "ES6", "moduleResolution": "node", "target": "ES5", "outDir": "lib", "lib": ["ES5", "ES2015.Promise", "ES2015.Iterable", "DOM"], "importHelpers": true, "types": [], "rootDir": "src" }, "include": ["src/*"], "references": [ { "path": "../algorithm" }, { "path": "../coreutils" }, { "path": "../disposable" }, { "path": "../domutils" }, { "path": "../keyboard" }, { "path": "../signaling" }, { "path": "../virtualdom" } ] } lumino-2021.12.13/packages/coreutils/000077500000000000000000000000001415564225700172105ustar00rootroot00000000000000lumino-2021.12.13/packages/coreutils/api-extractor.json000066400000000000000000000014611415564225700226670ustar00rootroot00000000000000/** * Config file for API Extractor. For more info, please visit: https://api-extractor.com */ { "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", /** * Optionally specifies another JSON config file that this file extends from. This provides a way for * standard settings to be shared across multiple projects. * * If the path starts with "./" or "../", the path is resolved relative to the folder of the file that contains * the "extends" field. Otherwise, the first path segment is interpreted as an NPM package name, and will be * resolved using NodeJS require(). * * SUPPORTED TOKENS: none * DEFAULT VALUE: "" */ "extends": "../../api-extractor-base.json" // "extends": "my-package/include/api-extractor-base.json" } lumino-2021.12.13/packages/coreutils/package.json000066400000000000000000000051401415564225700214760ustar00rootroot00000000000000{ "name": "@lumino/coreutils", "version": "1.11.1", "description": "Lumino Core Utilities", "homepage": "https://github.com/jupyterlab/lumino", "bugs": { "url": "https://github.com/jupyterlab/lumino/issues" }, "repository": { "type": "git", "url": "https://github.com/jupyterlab/lumino.git" }, "license": "BSD-3-Clause", "author": "S. Chris Colbert ", "contributors": [ "A. Darian ", "Dave Willmer ", "S. Chris Colbert ", "Steven Silvester " ], "main": "dist/index.js", "jsdelivr": "dist/index.min.js", "unpkg": "dist/index.min.js", "module": "dist/index.es6", "browser": { "crypto": false }, "types": "types/index.d.ts", "files": [ "dist/*", "src/*", "types/*" ], "scripts": { "api": "api-extractor run --local --verbose", "build": "npm run build:src && rollup -c", "build:src": "tsc --build", "build:test": "tsc --build tests && cd tests && webpack", "clean": "rimraf ./lib && rimraf *.tsbuildinfo && rimraf ./types && rimraf ./dist", "clean:test": "rimraf tests/build", "docs": "typedoc --options tdoptions.json src", "minimize": "terser dist/index.js -c -m --source-map \"content='dist/index.js.map',url='index.min.js.map'\" -o dist/index.min.js", "test": "npm run test:firefox-headless", "test:chrome": "cd tests && karma start --browsers=Chrome", "test:chrome-headless": "cd tests && karma start --browsers=ChromeHeadless", "test:firefox": "cd tests && karma start --browsers=Firefox", "test:firefox-headless": "cd tests && karma start --browsers=FirefoxHeadless", "test:ie": "cd tests && karma start --browsers=IE", "watch": "tsc --build --watch" }, "devDependencies": { "@microsoft/api-extractor": "^7.6.0", "@types/chai": "^3.4.35", "@types/mocha": "^2.2.39", "chai": "^4.3.4", "es6-promise": "^4.0.5", "karma": "^6.3.4", "karma-chrome-launcher": "^3.1.0", "karma-firefox-launcher": "^2.1.1", "karma-ie-launcher": "^1.0.0", "karma-mocha": "^2.0.1", "karma-mocha-reporter": "^2.2.5", "mocha": "^9.0.3", "rimraf": "^3.0.2", "rollup": "^2.56.0", "rollup-plugin-node-resolve": "^5.2.0", "rollup-plugin-postcss": "^4.0.0", "rollup-plugin-sourcemaps": "^0.6.3", "terser": "^5.7.1", "tslib": "^2.3.0", "typedoc": "~0.15.0", "typescript": "~3.6.0", "webpack": "^4.41.3", "webpack-cli": "^3.3.10" }, "peerDependencies": { "crypto": "1.0.1" }, "publishConfig": { "access": "public" } } lumino-2021.12.13/packages/coreutils/rollup.config.js000066400000000000000000000016311415564225700223300ustar00rootroot00000000000000import nodeResolve from 'rollup-plugin-node-resolve'; import sourcemaps from 'rollup-plugin-sourcemaps'; import postcss from 'rollup-plugin-postcss'; const pkg = require('./package.json'); const globals = id => id.indexOf('@lumino/') === 0 ? id.replace('@lumino/', 'lumino_') : id; export default [ { input: 'lib/index', external: id => (pkg.dependencies && !!pkg.dependencies[id]) || (pkg.peerDependencies && !!pkg.peerDependencies[id]), output: [ { file: pkg.main, globals, format: 'umd', sourcemap: true, name: pkg.name }, { file: pkg.module + '.js', format: 'es', sourcemap: true, name: pkg.name } ], plugins: [ nodeResolve({ preferBuiltins: true }), sourcemaps(), postcss({ extensions: ['.css'], minimize: true }) ] } ]; lumino-2021.12.13/packages/coreutils/src/000077500000000000000000000000001415564225700177775ustar00rootroot00000000000000lumino-2021.12.13/packages/coreutils/src/index.ts000066400000000000000000000011321415564225700214530ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ export * from './json'; export * from './mime'; export * from './promise'; export * from './random'; export * from './token'; export * from './uuid'; lumino-2021.12.13/packages/coreutils/src/json.ts000066400000000000000000000214041415564225700213210ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ /** * A type alias for a JSON primitive. */ export type JSONPrimitive = boolean | number | string | null; /** * A type alias for a JSON value. */ export type JSONValue = JSONPrimitive | JSONObject | JSONArray; /** * A type definition for a JSON object. */ export interface JSONObject { [key: string]: JSONValue; } /** * A type definition for a JSON array. */ export interface JSONArray extends Array {} /** * A type definition for a readonly JSON object. */ export interface ReadonlyJSONObject { readonly [key: string]: ReadonlyJSONValue; } /** * A type definition for a readonly JSON array. */ export interface ReadonlyJSONArray extends ReadonlyArray {} /** * A type alias for a readonly JSON value. */ export type ReadonlyJSONValue = | JSONPrimitive | ReadonlyJSONObject | ReadonlyJSONArray; /** * A type alias for a partial JSON value. * * Note: Partial here means that JSON object attributes can be `undefined`. */ export type PartialJSONValue = | JSONPrimitive | PartialJSONObject | PartialJSONArray; /** * A type definition for a partial JSON object. * * Note: Partial here means that the JSON object attributes can be `undefined`. */ export interface PartialJSONObject { [key: string]: PartialJSONValue | undefined; } /** * A type definition for a partial JSON array. * * Note: Partial here means that JSON object attributes can be `undefined`. */ export interface PartialJSONArray extends Array {} /** * A type definition for a readonly partial JSON object. * * Note: Partial here means that JSON object attributes can be `undefined`. */ export interface ReadonlyPartialJSONObject { readonly [key: string]: ReadonlyPartialJSONValue | undefined; } /** * A type definition for a readonly partial JSON array. * * Note: Partial here means that JSON object attributes can be `undefined`. */ export interface ReadonlyPartialJSONArray extends ReadonlyArray {} /** * A type alias for a readonly partial JSON value. * * Note: Partial here means that JSON object attributes can be `undefined`. */ export type ReadonlyPartialJSONValue = | JSONPrimitive | ReadonlyPartialJSONObject | ReadonlyPartialJSONArray; /** * The namespace for JSON-specific functions. */ export namespace JSONExt { /** * A shared frozen empty JSONObject */ export const emptyObject = Object.freeze({}) as ReadonlyJSONObject; /** * A shared frozen empty JSONArray */ export const emptyArray = Object.freeze([]) as ReadonlyJSONArray; /** * Test whether a JSON value is a primitive. * * @param value - The JSON value of interest. * * @returns `true` if the value is a primitive,`false` otherwise. */ export function isPrimitive( value: ReadonlyPartialJSONValue ): value is JSONPrimitive { return ( value === null || typeof value === 'boolean' || typeof value === 'number' || typeof value === 'string' ); } /** * Test whether a JSON value is an array. * * @param value - The JSON value of interest. * * @returns `true` if the value is a an array, `false` otherwise. */ export function isArray(value: JSONValue): value is JSONArray; export function isArray(value: ReadonlyJSONValue): value is ReadonlyJSONArray; export function isArray(value: PartialJSONValue): value is PartialJSONArray; export function isArray( value: ReadonlyPartialJSONValue ): value is ReadonlyPartialJSONArray; export function isArray(value: ReadonlyPartialJSONValue): boolean { return Array.isArray(value); } /** * Test whether a JSON value is an object. * * @param value - The JSON value of interest. * * @returns `true` if the value is a an object, `false` otherwise. */ export function isObject(value: JSONValue): value is JSONObject; export function isObject( value: ReadonlyJSONValue ): value is ReadonlyJSONObject; export function isObject(value: PartialJSONValue): value is PartialJSONObject; export function isObject( value: ReadonlyPartialJSONValue ): value is ReadonlyPartialJSONObject; export function isObject(value: ReadonlyPartialJSONValue): boolean { return !isPrimitive(value) && !isArray(value); } /** * Compare two JSON values for deep equality. * * @param first - The first JSON value of interest. * * @param second - The second JSON value of interest. * * @returns `true` if the values are equivalent, `false` otherwise. */ export function deepEqual( first: ReadonlyPartialJSONValue, second: ReadonlyPartialJSONValue ): boolean { // Check referential and primitive equality first. if (first === second) { return true; } // If one is a primitive, the `===` check ruled out the other. if (isPrimitive(first) || isPrimitive(second)) { return false; } // Test whether they are arrays. let a1 = isArray(first); let a2 = isArray(second); // Bail if the types are different. if (a1 !== a2) { return false; } // If they are both arrays, compare them. if (a1 && a2) { return deepArrayEqual( first as ReadonlyPartialJSONArray, second as ReadonlyPartialJSONArray ); } // At this point, they must both be objects. return deepObjectEqual( first as ReadonlyPartialJSONObject, second as ReadonlyPartialJSONObject ); } /** * Create a deep copy of a JSON value. * * @param value - The JSON value to copy. * * @returns A deep copy of the given JSON value. */ export function deepCopy(value: T): T { // Do nothing for primitive values. if (isPrimitive(value)) { return value; } // Deep copy an array. if (isArray(value)) { return deepArrayCopy(value); } // Deep copy an object. return deepObjectCopy(value); } /** * Compare two JSON arrays for deep equality. */ function deepArrayEqual( first: ReadonlyPartialJSONArray, second: ReadonlyPartialJSONArray ): boolean { // Check referential equality first. if (first === second) { return true; } // Test the arrays for equal length. if (first.length !== second.length) { return false; } // Compare the values for equality. for (let i = 0, n = first.length; i < n; ++i) { if (!deepEqual(first[i], second[i])) { return false; } } // At this point, the arrays are equal. return true; } /** * Compare two JSON objects for deep equality. */ function deepObjectEqual( first: ReadonlyPartialJSONObject, second: ReadonlyPartialJSONObject ): boolean { // Check referential equality first. if (first === second) { return true; } // Check for the first object's keys in the second object. for (let key in first) { if (first[key] !== undefined && !(key in second)) { return false; } } // Check for the second object's keys in the first object. for (let key in second) { if (second[key] !== undefined && !(key in first)) { return false; } } // Compare the values for equality. for (let key in first) { // Get the values. let firstValue = first[key]; let secondValue = second[key]; // If both are undefined, ignore the key. if (firstValue === undefined && secondValue === undefined) { continue; } // If only one value is undefined, the objects are not equal. if (firstValue === undefined || secondValue === undefined) { return false; } // Compare the values. if (!deepEqual(firstValue, secondValue)) { return false; } } // At this point, the objects are equal. return true; } /** * Create a deep copy of a JSON array. */ function deepArrayCopy(value: any): any { let result = new Array(value.length); for (let i = 0, n = value.length; i < n; ++i) { result[i] = deepCopy(value[i]); } return result; } /** * Create a deep copy of a JSON object. */ function deepObjectCopy(value: any): any { let result: any = {}; for (let key in value) { // Ignore undefined values. let subvalue = value[key]; if (subvalue === undefined) { continue; } result[key] = deepCopy(subvalue); } return result; } } lumino-2021.12.13/packages/coreutils/src/mime.ts000066400000000000000000000052251415564225700213020ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ /** * An object which stores MIME data for general application use. * * #### Notes * This class does not attempt to enforce "correctness" of MIME types * and their associated data. Since this class is designed to transfer * arbitrary data and objects within the same application, it assumes * that the user provides correct and accurate data. */ export class MimeData { /** * Get an array of the MIME types contained within the dataset. * * @returns A new array of the MIME types, in order of insertion. */ types(): string[] { return this._types.slice(); } /** * Test whether the dataset has an entry for the given type. * * @param mime - The MIME type of interest. * * @returns `true` if the dataset contains a value for the given * MIME type, `false` otherwise. */ hasData(mime: string): boolean { return this._types.indexOf(mime) !== -1; } /** * Get the data value for the given MIME type. * * @param mime - The MIME type of interest. * * @returns The value for the given MIME type, or `undefined` if * the dataset does not contain a value for the type. */ getData(mime: string): any | undefined { let i = this._types.indexOf(mime); return i !== -1 ? this._values[i] : undefined; } /** * Set the data value for the given MIME type. * * @param mime - The MIME type of interest. * * @param data - The data value for the given MIME type. * * #### Notes * This will overwrite any previous entry for the MIME type. */ setData(mime: string, data: any): void { this.clearData(mime); this._types.push(mime); this._values.push(data); } /** * Remove the data entry for the given MIME type. * * @param mime - The MIME type of interest. * * #### Notes * This is a no-op if there is no entry for the given MIME type. */ clearData(mime: string): void { let i = this._types.indexOf(mime); if (i !== -1) { this._types.splice(i, 1); this._values.splice(i, 1); } } /** * Remove all data entries from the dataset. */ clear(): void { this._types.length = 0; this._values.length = 0; } private _types: string[] = []; private _values: any[] = []; } lumino-2021.12.13/packages/coreutils/src/promise.ts000066400000000000000000000030031415564225700220210ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ /** * A class which wraps a promise into a delegate object. * * #### Notes * This class is useful when the logic to resolve or reject a promise * cannot be defined at the point where the promise is created. */ export class PromiseDelegate { /** * Construct a new promise delegate. */ constructor() { this.promise = new Promise((resolve, reject) => { this._resolve = resolve; this._reject = reject; }); } /** * The promise wrapped by the delegate. */ readonly promise: Promise; /** * Resolve the wrapped promise with the given value. * * @param value - The value to use for resolving the promise. */ resolve(value: T | PromiseLike): void { let resolve = this._resolve; resolve(value); } /** * Reject the wrapped promise with the given value. * * @reason - The reason for rejecting the promise. */ reject(reason: any): void { let reject = this._reject; reject(reason); } private _resolve: (value: T | PromiseLike) => void; private _reject: (reason: any) => void; } lumino-2021.12.13/packages/coreutils/src/random.ts000066400000000000000000000051251415564225700216320ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ // Declare ambient variables for `window` and `require` to avoid a // hard dependency on both. This package must run on node. declare let window: any; declare let require: any; /** * The namespace for random number related functionality. */ export namespace Random { /** * A function which generates random bytes. * * @param buffer - The `Uint8Array` to fill with random bytes. * * #### Notes * A cryptographically strong random number generator will be used if * available. Otherwise, `Math.random` will be used as a fallback for * randomness. * * The following RNGs are supported, listed in order of precedence: * - `window.crypto.getRandomValues` * - `window.msCrypto.getRandomValues` * - `require('crypto').randomFillSync * - `require('crypto').randomBytes * - `Math.random` */ export const getRandomValues = (() => { // Look up the crypto module if available. const crypto: any = (typeof window !== 'undefined' && (window.crypto || window.msCrypto)) || (typeof require !== 'undefined' && require('crypto')) || null; // Modern browsers and IE 11 if (crypto && typeof crypto.getRandomValues === 'function') { return function getRandomValues(buffer: Uint8Array): void { return crypto.getRandomValues(buffer); }; } // Node 7+ if (crypto && typeof crypto.randomFillSync === 'function') { return function getRandomValues(buffer: Uint8Array): void { return crypto.randomFillSync(buffer); }; } // Node 0.10+ if (crypto && typeof crypto.randomBytes === 'function') { return function getRandomValues(buffer: Uint8Array): void { let bytes = crypto.randomBytes(buffer.length); for (let i = 0, n = bytes.length; i < n; ++i) { buffer[i] = bytes[i]; } }; } // Fallback return function getRandomValues(buffer: Uint8Array): void { let value = 0; for (let i = 0, n = buffer.length; i < n; ++i) { if (i % 4 === 0) { value = (Math.random() * 0xffffffff) >>> 0; } buffer[i] = value & 0xff; value >>>= 8; } }; })(); } lumino-2021.12.13/packages/coreutils/src/token.ts000066400000000000000000000021361415564225700214710ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ /** * A runtime object which captures compile-time type information. * * #### Notes * A token captures the compile-time type of an interface or class in * an object which can be used at runtime in a type-safe fashion. */ export class Token { /** * Construct a new token. * * @param name - A human readable name for the token. */ constructor(name: string) { this.name = name; this._tokenStructuralPropertyT = null!; } /** * The human readable name for the token. * * #### Notes * This can be useful for debugging and logging. */ readonly name: string; // @ts-ignore private _tokenStructuralPropertyT: T; } lumino-2021.12.13/packages/coreutils/src/uuid.ts000066400000000000000000000044161415564225700213220ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { Random } from './random'; /** * The namespace for UUID related functionality. */ export namespace UUID { /** * A function which generates UUID v4 identifiers. * * @returns A new UUID v4 string. * * #### Notes * This implementation complies with RFC 4122. * * This uses `Random.getRandomValues()` for random bytes, which in * turn will use the underlying `crypto` module of the platform if * it is available. The fallback for randomness is `Math.random`. */ export const uuid4 = (() => { // Create a 16 byte array to hold the random values. const bytes = new Uint8Array(16); // Create a look up table from bytes to hex strings. const lut = new Array(256); // Pad the single character hex digits with a leading zero. for (let i = 0; i < 16; ++i) { lut[i] = '0' + i.toString(16); } // Populate the rest of the hex digits. for (let i = 16; i < 256; ++i) { lut[i] = i.toString(16); } // Return a function which generates the UUID. return function uuid4(): string { // Get a new batch of random values. Random.getRandomValues(bytes); // Set the UUID version number to 4. bytes[6] = 0x40 | (bytes[6] & 0x0f); // Set the clock sequence bit to the RFC spec. bytes[8] = 0x80 | (bytes[8] & 0x3f); // Assemble the UUID string. return ( lut[bytes[0]] + lut[bytes[1]] + lut[bytes[2]] + lut[bytes[3]] + '-' + lut[bytes[4]] + lut[bytes[5]] + '-' + lut[bytes[6]] + lut[bytes[7]] + '-' + lut[bytes[8]] + lut[bytes[9]] + '-' + lut[bytes[10]] + lut[bytes[11]] + lut[bytes[12]] + lut[bytes[13]] + lut[bytes[14]] + lut[bytes[15]] ); }; })(); } lumino-2021.12.13/packages/coreutils/tdoptions.json000066400000000000000000000005201415564225700221230ustar00rootroot00000000000000{ "excludeNotExported": true, "mode": "file", "target": "es5", "module": "es5", "lib": [ "lib.es2015.d.ts", "lib.es2015.collection.d.ts", "lib.es2015.promise.d.ts", "lib.dom.d.ts" ], "out": "../../docs/source/api/coreutils", "baseUrl": ".", "paths": { "@lumino/*": ["node_modules/@lumino/*"] } } lumino-2021.12.13/packages/coreutils/tests/000077500000000000000000000000001415564225700203525ustar00rootroot00000000000000lumino-2021.12.13/packages/coreutils/tests/karma.conf.js000066400000000000000000000005051415564225700227270ustar00rootroot00000000000000module.exports = function (config) { config.set({ basePath: '.', frameworks: ['mocha'], reporters: ['mocha'], files: ['build/bundle.test.js'], port: 9876, colors: true, singleRun: true, browserNoActivityTimeout: 30000, failOnEmptyTestSuite: false, logLevel: config.LOG_INFO }); }; lumino-2021.12.13/packages/coreutils/tests/src/000077500000000000000000000000001415564225700211415ustar00rootroot00000000000000lumino-2021.12.13/packages/coreutils/tests/src/index.spec.ts000066400000000000000000000011261415564225700235510ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import 'es6-promise/auto'; // polyfill Promise on IE import './json.spec'; import './mime.spec'; import './promise.spec'; import './token.spec'; lumino-2021.12.13/packages/coreutils/tests/src/json.spec.ts000066400000000000000000000131111415564225700234100ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { expect } from 'chai'; import { JSONArray, JSONExt, JSONObject, JSONPrimitive, PartialJSONObject } from '@lumino/coreutils'; interface IFoo extends PartialJSONObject { bar?: string; } describe('@lumino/coreutils', () => { describe('JSONExt', () => { describe('isPrimitive()', () => { it('should return `true` if the value is a primitive', () => { expect(JSONExt.isPrimitive(null)).to.equal(true); expect(JSONExt.isPrimitive(false)).to.equal(true); expect(JSONExt.isPrimitive(true)).to.equal(true); expect(JSONExt.isPrimitive(1)).to.equal(true); expect(JSONExt.isPrimitive('1')).to.equal(true); }); it('should return `false` if the value is not a primitive', () => { expect(JSONExt.isPrimitive([])).to.equal(false); expect(JSONExt.isPrimitive({})).to.equal(false); }); }); describe('isArray()', () => { it('should test whether a JSON value is an array', () => { expect(JSONExt.isArray([])).to.equal(true); expect(JSONExt.isArray(null)).to.equal(false); expect(JSONExt.isArray(1)).to.equal(false); }); }); describe('isObject()', () => { it('should test whether a JSON value is an object', () => { expect(JSONExt.isObject({ a: 1 })).to.equal(true); expect(JSONExt.isObject({})).to.equal(true); expect(JSONExt.isObject([])).to.equal(false); expect(JSONExt.isObject(1)).to.equal(false); }); }); describe('deepEqual()', () => { it('should compare two JSON values for deep equality', () => { expect(JSONExt.deepEqual([], [])).to.equal(true); expect(JSONExt.deepEqual([1], [1])).to.equal(true); expect(JSONExt.deepEqual({}, {})).to.equal(true); expect(JSONExt.deepEqual({ a: [] }, { a: [] })).to.equal(true); expect( JSONExt.deepEqual({ a: { b: null } }, { a: { b: null } }) ).to.equal(true); expect(JSONExt.deepEqual({ a: '1' }, { a: '1' })).to.equal(true); expect( JSONExt.deepEqual({ a: { b: null } }, { a: { b: '1' } }) ).to.equal(false); expect(JSONExt.deepEqual({ a: [] }, { a: [1] })).to.equal(false); expect(JSONExt.deepEqual([1], [1, 2])).to.equal(false); expect(JSONExt.deepEqual(null, [1, 2])).to.equal(false); expect(JSONExt.deepEqual([1], {})).to.equal(false); expect(JSONExt.deepEqual([1], [2])).to.equal(false); expect(JSONExt.deepEqual({}, { a: 1 })).to.equal(false); expect(JSONExt.deepEqual({ b: 1 }, { a: 1 })).to.equal(false); }); it('should handle optional keys on partials', () => { let a: IFoo = {}; let b: IFoo = { bar: 'a' }; let c: IFoo = { bar: undefined }; expect(JSONExt.deepEqual(a, b)).to.equal(false); expect(JSONExt.deepEqual(a, c)).to.equal(true); expect(JSONExt.deepEqual(c, a)).to.equal(true); }); it('should equate an object to its deepCopy', () => { let a: IFoo = {}; let b: IFoo = { bar: 'a' }; let c: IFoo = { bar: undefined }; expect(JSONExt.deepEqual(a, JSONExt.deepCopy(a))).to.equal(true); expect(JSONExt.deepEqual(b, JSONExt.deepCopy(b))).to.equal(true); expect(JSONExt.deepEqual(c, JSONExt.deepCopy(c))).to.equal(true); }); }); describe('deepCopy()', () => { it('should deep copy an object', () => { let v1: JSONPrimitive = null; let v2: JSONPrimitive = true; let v3: JSONPrimitive = false; let v4: JSONPrimitive = 'foo'; let v5: JSONPrimitive = 42; let v6: JSONArray = [1, 2, 3, [4, 5, 6], { a: 12, b: [4, 5] }, false]; let v7: JSONObject = { a: false, b: [null, [1, 2]], c: { a: 1 } }; let r1 = JSONExt.deepCopy(v1); let r2 = JSONExt.deepCopy(v2); let r3 = JSONExt.deepCopy(v3); let r4 = JSONExt.deepCopy(v4); let r5 = JSONExt.deepCopy(v5); let r6 = JSONExt.deepCopy(v6); let r7 = JSONExt.deepCopy(v7); expect(v1).to.equal(r1); expect(v2).to.equal(r2); expect(v3).to.equal(r3); expect(v4).to.equal(r4); expect(v5).to.equal(r5); expect(v6).to.deep.equal(r6); expect(v7).to.deep.equal(r7); expect(v6).to.not.equal(r6); expect(v6[3]).to.not.equal(r6[3]); expect(v6[4]).to.not.equal(r6[4]); expect((v6[4] as JSONObject)['b']).to.not.equal( (r6[4] as JSONObject)['b'] ); expect(v7).to.not.equal(r7); expect(v7['b']).to.not.equal(r7['b']); expect((v7['b'] as JSONArray)[1]).to.not.equal( (r7['b'] as JSONArray)[1] ); expect(v7['c']).to.not.equal(r7['c']); }); it('should handle optional keys on partials', () => { let v1: IFoo = {}; let v2: IFoo = { bar: 'a' }; let v3 = { a: false, b: { bar: undefined } }; let r1 = JSONExt.deepCopy(v1); let r2 = JSONExt.deepCopy(v2); let r3 = JSONExt.deepCopy(v3); expect(Object.keys(r1).length).to.equal(0); expect(v2.bar).to.equal(r2.bar); expect(Object.keys(r3.b).length).to.equal(0); }); }); }); }); lumino-2021.12.13/packages/coreutils/tests/src/mime.spec.ts000066400000000000000000000071311415564225700233730ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { expect } from 'chai'; import { MimeData } from '@lumino/coreutils'; describe('@lumino/coreutils', () => { describe('MimeData', () => { describe('#types()', () => { it('should get an array of the MIME types contained within the dataset', () => { let data = new MimeData(); data.setData('foo', 1); data.setData('bar', '1'); data.setData('baz', { foo: 1, bar: 2 }); expect(data.types()).to.deep.equal(['foo', 'bar', 'baz']); }); it('should be in order of insertion', () => { let data = new MimeData(); data.setData('a', 1); data.setData('b', '1'); data.setData('c', { foo: 1, bar: 2 }); data.setData('a', 4); expect(data.types()).to.deep.equal(['b', 'c', 'a']); data.setData('d', null); expect(data.types()).to.deep.equal(['b', 'c', 'a', 'd']); }); }); describe('#hasData()', () => { it('should return `true` if the dataset contains the value', () => { let data = new MimeData(); data.setData('foo', 1); expect(data.hasData('foo')).to.equal(true); }); it('should return `false` if the dataset does not contain the value', () => { let data = new MimeData(); data.setData('foo', 1); expect(data.hasData('bar')).to.equal(false); }); }); describe('#getData()', () => { it('should get the value for the given MIME type', () => { let data = new MimeData(); let value = { foo: 1, bar: '10' }; data.setData('baz', value); expect(data.getData('baz')).to.equal(value); }); it('should return `undefined` if the dataset does not contain a value for the type', () => { let data = new MimeData(); expect(data.getData('foo')).to.equal(undefined); }); }); describe('#setData()', () => { it('should set the data value for the given MIME type', () => { let data = new MimeData(); let value = { foo: 1, bar: '10' }; data.setData('baz', value); expect(data.getData('baz')).to.equal(value); }); it('should overwrite any previous entry for the MIME type', () => { let data = new MimeData(); data.setData('foo', 1); data.setData('foo', 2); expect(data.getData('foo')).to.equal(2); }); }); describe('#clearData()', () => { it('should remove the data entry for the given MIME type', () => { let data = new MimeData(); data.setData('foo', 1); data.clearData('foo'); expect(data.getData('foo')).to.equal(undefined); }); it('should be a no-op if there is no entry for the given MIME type', () => { let data = new MimeData(); data.clearData('foo'); expect(data.getData('foo')).to.equal(undefined); }); }); describe('#clear()', () => { it('should remove all entries from the dataset', () => { let data = new MimeData(); data.setData('foo', 1); data.setData('bar', '1'); data.setData('baz', { foo: 1, bar: 2 }); data.clear(); expect(data.types()).to.deep.equal([]); }); }); }); }); lumino-2021.12.13/packages/coreutils/tests/src/promise.spec.ts000066400000000000000000000036661415564225700241330ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { expect } from 'chai'; import { PromiseDelegate } from '@lumino/coreutils'; describe('@lumino/coreutils', () => { describe('PromiseDelegate', () => { describe('#constructor()', () => { it('should create a new promise delegate', () => { let delegate = new PromiseDelegate(); expect(delegate).to.be.an.instanceof(PromiseDelegate); }); }); describe('#promise', () => { it('should get the underlying promise', () => { let delegate = new PromiseDelegate(); expect(delegate.promise).to.be.an.instanceof(Promise); }); }); describe('#resolve()', () => { it('should resolve the underlying promise', done => { let delegate = new PromiseDelegate(); delegate.promise.then(value => { expect(value).to.equal(1); done(); }); delegate.resolve(1); }); it('should accept a promise to the value', done => { let delegate = new PromiseDelegate(); delegate.promise.then(value => { expect(value).to.equal(4); done(); }); delegate.resolve(Promise.resolve(4)); }); }); describe('#reject()', () => { it('should reject the underlying promise', done => { let delegate = new PromiseDelegate(); delegate.promise.catch(reason => { expect(reason).to.equal('foo'); done(); }); delegate.reject('foo'); }); }); }); }); lumino-2021.12.13/packages/coreutils/tests/src/token.spec.ts000066400000000000000000000021271415564225700235640ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { expect } from 'chai'; import { Token } from '@lumino/coreutils'; interface ITestInterface { foo: number; bar: string; } describe('@lumino/coreutils', () => { describe('Token', () => { describe('#constructor', () => { it('should accept a name for the token', () => { let token = new Token('ITestInterface'); expect(token).to.be.an.instanceof(Token); }); }); describe('#name', () => { it('should be the name for the token', () => { let token = new Token('ITestInterface'); expect(token.name).to.equal('ITestInterface'); }); }); }); }); lumino-2021.12.13/packages/coreutils/tests/tsconfig.json000066400000000000000000000006251415564225700230640ustar00rootroot00000000000000{ "compilerOptions": { "declaration": false, "noImplicitAny": true, "noEmitOnError": true, "noUnusedLocals": true, "strictNullChecks": true, "module": "commonjs", "moduleResolution": "node", "target": "ES5", "outDir": "build", "lib": ["ES5", "ES2015.Promise", "ES2015.Iterable"], "types": ["chai", "mocha"], "rootDir": "src" }, "include": ["src/*"] } lumino-2021.12.13/packages/coreutils/tests/webpack.config.js000066400000000000000000000003061415564225700235670ustar00rootroot00000000000000var path = require('path'); module.exports = { entry: './build/index.spec.js', mode: 'development', output: { filename: './build/bundle.test.js', path: path.resolve(__dirname) } }; lumino-2021.12.13/packages/coreutils/tsconfig.json000066400000000000000000000010041415564225700217120ustar00rootroot00000000000000{ "compilerOptions": { "composite": true, "sourceMap": true, "declaration": true, "declarationDir": "./types", "declarationMap": true, "noImplicitAny": true, "noEmitOnError": true, "noUnusedLocals": true, "strictNullChecks": true, "module": "ES6", "moduleResolution": "node", "target": "ES5", "outDir": "lib", "lib": ["ES5", "ES2015.Promise", "ES2015.Iterable"], "importHelpers": true, "types": [], "rootDir": "src" }, "include": ["src/*"] } lumino-2021.12.13/packages/datagrid/000077500000000000000000000000001415564225700167565ustar00rootroot00000000000000lumino-2021.12.13/packages/datagrid/api-extractor.json000066400000000000000000000014611415564225700224350ustar00rootroot00000000000000/** * Config file for API Extractor. For more info, please visit: https://api-extractor.com */ { "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", /** * Optionally specifies another JSON config file that this file extends from. This provides a way for * standard settings to be shared across multiple projects. * * If the path starts with "./" or "../", the path is resolved relative to the folder of the file that contains * the "extends" field. Otherwise, the first path segment is interpreted as an NPM package name, and will be * resolved using NodeJS require(). * * SUPPORTED TOKENS: none * DEFAULT VALUE: "" */ "extends": "../../api-extractor-base.json" // "extends": "my-package/include/api-extractor-base.json" } lumino-2021.12.13/packages/datagrid/package.json000066400000000000000000000036111415564225700212450ustar00rootroot00000000000000{ "name": "@lumino/datagrid", "version": "0.34.1", "description": "Lumino Tabular Data Grid", "homepage": "https://github.com/jupyterlab/lumino", "bugs": { "url": "https://github.com/jupyterlab/lumino/issues" }, "repository": { "type": "git", "url": "https://github.com/jupyterlab/lumino.git" }, "license": "BSD-3-Clause", "author": "S. Chris Colbert ", "contributors": [ "A. Darian ", "S. Chris Colbert ", "Steven Silvester " ], "main": "dist/index.js", "jsdelivr": "dist/index.min.js", "unpkg": "dist/index.min.js", "module": "dist/index.es6", "types": "types/index.d.ts", "files": [ "dist/*", "src/*", "types/*" ], "scripts": { "api": "api-extractor run --local --verbose", "build": "npm run build:src && rollup -c", "build:src": "tsc --build", "clean": "rimraf ./lib && rimraf *.tsbuildinfo && rimraf ./types && rimraf ./dist", "docs": "typedoc --options tdoptions.json src", "minimize": "terser dist/index.js -c -m --source-map \"content='dist/index.js.map',url='index.min.js.map'\" -o dist/index.min.js", "watch": "tsc --build --watch" }, "dependencies": { "@lumino/algorithm": "^1.9.1", "@lumino/coreutils": "^1.11.1", "@lumino/disposable": "^1.10.1", "@lumino/domutils": "^1.8.1", "@lumino/dragdrop": "^1.13.1", "@lumino/keyboard": "^1.8.1", "@lumino/messaging": "^1.10.1", "@lumino/signaling": "^1.10.1", "@lumino/widgets": "^1.30.1" }, "devDependencies": { "rimraf": "^3.0.2", "rollup": "^2.56.0", "rollup-plugin-node-resolve": "^5.2.0", "rollup-plugin-postcss": "^4.0.0", "rollup-plugin-sourcemaps": "^0.6.3", "terser": "^5.7.1", "tslib": "^2.3.0", "typedoc": "~0.15.0", "typescript": "~3.6.0" }, "publishConfig": { "access": "public" } } lumino-2021.12.13/packages/datagrid/rollup.config.js000066400000000000000000000015231415564225700220760ustar00rootroot00000000000000import nodeResolve from 'rollup-plugin-node-resolve'; import sourcemaps from 'rollup-plugin-sourcemaps'; import postcss from 'rollup-plugin-postcss'; const pkg = require('./package.json'); const globals = id => id.indexOf('@lumino/') === 0 ? id.replace('@lumino/', 'lumino_') : id; export default [ { input: 'lib/index', external: id => pkg.dependencies && !!pkg.dependencies[id], output: [ { file: pkg.main, globals, format: 'umd', sourcemap: true, name: pkg.name }, { file: pkg.module + '.js', format: 'es', sourcemap: true, name: pkg.name } ], plugins: [ nodeResolve({ preferBuiltins: true }), sourcemaps(), postcss({ extensions: ['.css'], minimize: true }) ] } ]; lumino-2021.12.13/packages/datagrid/src/000077500000000000000000000000001415564225700175455ustar00rootroot00000000000000lumino-2021.12.13/packages/datagrid/src/basickeyhandler.ts000066400000000000000000000462671415564225700232640ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2019, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { Platform } from '@lumino/domutils'; import { getKeyboardLayout } from '@lumino/keyboard'; import { DataGrid } from './datagrid'; import { SelectionModel } from './selectionmodel'; import { CellEditor } from './celleditor'; import { MutableDataModel } from './datamodel'; /** * A basic implementation of a data grid key handler. * * #### Notes * This class may be subclassed and customized as needed. */ export class BasicKeyHandler implements DataGrid.IKeyHandler { /** * Whether the key handler is disposed. */ get isDisposed(): boolean { return this._disposed; } /** * Dispose of the resources held by the key handler. */ dispose(): void { this._disposed = true; } /** * Handle the key down event for the data grid. * * @param grid - The data grid of interest. * * @param event - The keydown event of interest. * * #### Notes * This will not be called if the mouse button is pressed. */ onKeyDown(grid: DataGrid, event: KeyboardEvent): void { // if grid is editable and cell selection available, start cell editing // on key press (letters, numbers and space only) if ( grid.editable && grid.selectionModel!.cursorRow !== -1 && grid.selectionModel!.cursorColumn !== -1 ) { const input = String.fromCharCode(event.keyCode); if (/[a-zA-Z0-9-_ ]/.test(input)) { const row = grid.selectionModel!.cursorRow; const column = grid.selectionModel!.cursorColumn; const cell: CellEditor.CellConfig = { grid: grid, row: row, column: column }; grid.editorController!.edit(cell); if (getKeyboardLayout().keyForKeydownEvent(event) === 'Space') { event.stopPropagation(); event.preventDefault(); } return; } } switch (getKeyboardLayout().keyForKeydownEvent(event)) { case 'ArrowLeft': this.onArrowLeft(grid, event); break; case 'ArrowRight': this.onArrowRight(grid, event); break; case 'ArrowUp': this.onArrowUp(grid, event); break; case 'ArrowDown': this.onArrowDown(grid, event); break; case 'PageUp': this.onPageUp(grid, event); break; case 'PageDown': this.onPageDown(grid, event); break; case 'Escape': this.onEscape(grid, event); break; case 'Delete': this.onDelete(grid, event); break; case 'C': this.onKeyC(grid, event); break; case 'Enter': if (grid.selectionModel) { grid.moveCursor(event.shiftKey ? 'up' : 'down'); grid.scrollToCursor(); } break; case 'Tab': if (grid.selectionModel) { grid.moveCursor(event.shiftKey ? 'left' : 'right'); grid.scrollToCursor(); event.stopPropagation(); event.preventDefault(); } break; } } /** * Handle the `'ArrowLeft'` key press for the data grid. * * @param grid - The data grid of interest. * * @param event - The keyboard event of interest. */ protected onArrowLeft(grid: DataGrid, event: KeyboardEvent): void { // Stop the event propagation. event.preventDefault(); event.stopPropagation(); // Fetch the selection model. let model = grid.selectionModel; // Fetch the modifier flags. let shift = event.shiftKey; let accel = Platform.accelKey(event); // Handle no model with the accel modifier. if (!model && accel) { grid.scrollTo(0, grid.scrollY); return; } // Handle no model and no modifier. (ignore shift) if (!model) { grid.scrollByStep('left'); return; } // Fetch the selection mode. let mode = model.selectionMode; // Handle the row selection mode with accel key. if (mode === 'row' && accel) { grid.scrollTo(0, grid.scrollY); return; } // Handle the row selection mode with no modifier. (ignore shift) if (mode === 'row') { grid.scrollByStep('left'); return; } // Fetch the cursor and selection. let r = model.cursorRow; let c = model.cursorColumn; let cs = model.currentSelection(); // Set up the selection variables. let r1: number; let r2: number; let c1: number; let c2: number; let cr: number; let cc: number; let clear: SelectionModel.ClearMode; // Dispatch based on the modifier keys. if (accel && shift) { r1 = cs ? cs.r1 : 0; r2 = cs ? cs.r2 : 0; c1 = cs ? cs.c1 : 0; c2 = 0; cr = r; cc = c; clear = 'current'; } else if (shift) { r1 = cs ? cs.r1 : 0; r2 = cs ? cs.r2 : 0; c1 = cs ? cs.c1 : 0; c2 = cs ? cs.c2 - 1 : 0; cr = r; cc = c; clear = 'current'; } else if (accel) { r1 = r; r2 = r; c1 = 0; c2 = 0; cr = r1; cc = c1; clear = 'all'; } else { r1 = r; r2 = r; c1 = c - 1; c2 = c - 1; cr = r1; cc = c1; clear = 'all'; } // Create the new selection. model.select({ r1, c1, r2, c2, cursorRow: cr, cursorColumn: cc, clear }); // Re-fetch the current selection. cs = model.currentSelection(); // Bail if there is no selection. if (!cs) { return; } // Scroll the grid appropriately. if (shift || mode === 'column') { grid.scrollToColumn(cs.c2); } else { grid.scrollToCursor(); } } /** * Handle the `'ArrowRight'` key press for the data grid. * * @param grid - The data grid of interest. * * @param event - The keyboard event of interest. */ protected onArrowRight(grid: DataGrid, event: KeyboardEvent): void { // Stop the event propagation. event.preventDefault(); event.stopPropagation(); // Fetch the selection model. let model = grid.selectionModel; // Fetch the modifier flags. let shift = event.shiftKey; let accel = Platform.accelKey(event); // Handle no model with the accel modifier. if (!model && accel) { grid.scrollTo(grid.maxScrollX, grid.scrollY); return; } // Handle no model and no modifier. (ignore shift) if (!model) { grid.scrollByStep('right'); return; } // Fetch the selection mode. let mode = model.selectionMode; // Handle the row selection model with accel key. if (mode === 'row' && accel) { grid.scrollTo(grid.maxScrollX, grid.scrollY); return; } // Handle the row selection mode with no modifier. (ignore shift) if (mode === 'row') { grid.scrollByStep('right'); return; } // Fetch the cursor and selection. let r = model.cursorRow; let c = model.cursorColumn; let cs = model.currentSelection(); // Set up the selection variables. let r1: number; let r2: number; let c1: number; let c2: number; let cr: number; let cc: number; let clear: SelectionModel.ClearMode; // Dispatch based on the modifier keys. if (accel && shift) { r1 = cs ? cs.r1 : 0; r2 = cs ? cs.r2 : 0; c1 = cs ? cs.c1 : 0; c2 = Infinity; cr = r; cc = c; clear = 'current'; } else if (shift) { r1 = cs ? cs.r1 : 0; r2 = cs ? cs.r2 : 0; c1 = cs ? cs.c1 : 0; c2 = cs ? cs.c2 + 1 : 0; cr = r; cc = c; clear = 'current'; } else if (accel) { r1 = r; r2 = r; c1 = Infinity; c2 = Infinity; cr = r1; cc = c1; clear = 'all'; } else { r1 = r; r2 = r; c1 = c + 1; c2 = c + 1; cr = r1; cc = c1; clear = 'all'; } // Create the new selection. model.select({ r1, c1, r2, c2, cursorRow: cr, cursorColumn: cc, clear }); // Re-fetch the current selection. cs = model.currentSelection(); // Bail if there is no selection. if (!cs) { return; } // Scroll the grid appropriately. if (shift || mode === 'column') { grid.scrollToColumn(cs.c2); } else { grid.scrollToCursor(); } } /** * Handle the `'ArrowUp'` key press for the data grid. * * @param grid - The data grid of interest. * * @param event - The keyboard event of interest. */ protected onArrowUp(grid: DataGrid, event: KeyboardEvent): void { // Stop the event propagation. event.preventDefault(); event.stopPropagation(); // Fetch the selection model. let model = grid.selectionModel; // Fetch the modifier flags. let shift = event.shiftKey; let accel = Platform.accelKey(event); // Handle no model with the accel modifier. if (!model && accel) { grid.scrollTo(grid.scrollX, 0); return; } // Handle no model and no modifier. (ignore shift) if (!model) { grid.scrollByStep('up'); return; } // Fetch the selection mode. let mode = model.selectionMode; // Handle the column selection mode with accel key. if (mode === 'column' && accel) { grid.scrollTo(grid.scrollX, 0); return; } // Handle the column selection mode with no modifier. (ignore shift) if (mode === 'column') { grid.scrollByStep('up'); return; } // Fetch the cursor and selection. let r = model.cursorRow; let c = model.cursorColumn; let cs = model.currentSelection(); // Set up the selection variables. let r1: number; let r2: number; let c1: number; let c2: number; let cr: number; let cc: number; let clear: SelectionModel.ClearMode; // Dispatch based on the modifier keys. if (accel && shift) { r1 = cs ? cs.r1 : 0; r2 = 0; c1 = cs ? cs.c1 : 0; c2 = cs ? cs.c2 : 0; cr = r; cc = c; clear = 'current'; } else if (shift) { r1 = cs ? cs.r1 : 0; r2 = cs ? cs.r2 - 1 : 0; c1 = cs ? cs.c1 : 0; c2 = cs ? cs.c2 : 0; cr = r; cc = c; clear = 'current'; } else if (accel) { r1 = 0; r2 = 0; c1 = c; c2 = c; cr = r1; cc = c1; clear = 'all'; } else { r1 = r - 1; r2 = r - 1; c1 = c; c2 = c; cr = r1; cc = c1; clear = 'all'; } // Create the new selection. model.select({ r1, c1, r2, c2, cursorRow: cr, cursorColumn: cc, clear }); // Re-fetch the current selection. cs = model.currentSelection(); // Bail if there is no selection. if (!cs) { return; } // Scroll the grid appropriately. if (shift || mode === 'row') { grid.scrollToRow(cs.r2); } else { grid.scrollToCursor(); } } /** * Handle the `'ArrowDown'` key press for the data grid. * * @param grid - The data grid of interest. * * @param event - The keyboard event of interest. */ protected onArrowDown(grid: DataGrid, event: KeyboardEvent): void { // Stop the event propagation. event.preventDefault(); event.stopPropagation(); // Fetch the selection model. let model = grid.selectionModel; // Fetch the modifier flags. let shift = event.shiftKey; let accel = Platform.accelKey(event); // Handle no model with the accel modifier. if (!model && accel) { grid.scrollTo(grid.scrollX, grid.maxScrollY); return; } // Handle no model and no modifier. (ignore shift) if (!model) { grid.scrollByStep('down'); return; } // Fetch the selection mode. let mode = model.selectionMode; // Handle the column selection mode with accel key. if (mode === 'column' && accel) { grid.scrollTo(grid.scrollX, grid.maxScrollY); return; } // Handle the column selection mode with no modifier. (ignore shift) if (mode === 'column') { grid.scrollByStep('down'); return; } // Fetch the cursor and selection. let r = model.cursorRow; let c = model.cursorColumn; let cs = model.currentSelection(); // Set up the selection variables. let r1: number; let r2: number; let c1: number; let c2: number; let cr: number; let cc: number; let clear: SelectionModel.ClearMode; // Dispatch based on the modifier keys. if (accel && shift) { r1 = cs ? cs.r1 : 0; r2 = Infinity; c1 = cs ? cs.c1 : 0; c2 = cs ? cs.c2 : 0; cr = r; cc = c; clear = 'current'; } else if (shift) { r1 = cs ? cs.r1 : 0; r2 = cs ? cs.r2 + 1 : 0; c1 = cs ? cs.c1 : 0; c2 = cs ? cs.c2 : 0; cr = r; cc = c; clear = 'current'; } else if (accel) { r1 = Infinity; r2 = Infinity; c1 = c; c2 = c; cr = r1; cc = c1; clear = 'all'; } else { r1 = r + 1; r2 = r + 1; c1 = c; c2 = c; cr = r1; cc = c1; clear = 'all'; } // Create the new selection. model.select({ r1, c1, r2, c2, cursorRow: cr, cursorColumn: cc, clear }); // Re-fetch the current selection. cs = model.currentSelection(); // Bail if there is no selection. if (!cs) { return; } // Scroll the grid appropriately. if (shift || mode === 'row') { grid.scrollToRow(cs.r2); } else { grid.scrollToCursor(); } } /** * Handle the `'PageUp'` key press for the data grid. * * @param grid - The data grid of interest. * * @param event - The keyboard event of interest. */ protected onPageUp(grid: DataGrid, event: KeyboardEvent): void { // Ignore the event if the accel key is pressed. if (Platform.accelKey(event)) { return; } // Stop the event propagation. event.preventDefault(); event.stopPropagation(); // Fetch the selection model. let model = grid.selectionModel; // Scroll by page if there is no selection model. if (!model || model.selectionMode === 'column') { grid.scrollByPage('up'); return; } // Get the normal number of cells in the page height. let n = Math.floor(grid.pageHeight / grid.defaultSizes.rowHeight); // Fetch the cursor and selection. let r = model.cursorRow; let c = model.cursorColumn; let cs = model.currentSelection(); // Set up the selection variables. let r1: number; let r2: number; let c1: number; let c2: number; let cr: number; let cc: number; let clear: SelectionModel.ClearMode; // Select or resize as needed. if (event.shiftKey) { r1 = cs ? cs.r1 : 0; r2 = cs ? cs.r2 - n : 0; c1 = cs ? cs.c1 : 0; c2 = cs ? cs.c2 : 0; cr = r; cc = c; clear = 'current'; } else { r1 = cs ? cs.r1 - n : 0; r2 = r1; c1 = c; c2 = c; cr = r1; cc = c; clear = 'all'; } // Create the new selection. model.select({ r1, c1, r2, c2, cursorRow: cr, cursorColumn: cc, clear }); // Re-fetch the current selection. cs = model.currentSelection(); // Bail if there is no selection. if (!cs) { return; } // Scroll the grid appropriately. grid.scrollToRow(cs.r2); } /** * Handle the `'PageDown'` key press for the data grid. * * @param grid - The data grid of interest. * * @param event - The keyboard event of interest. */ protected onPageDown(grid: DataGrid, event: KeyboardEvent): void { // Ignore the event if the accel key is pressed. if (Platform.accelKey(event)) { return; } // Stop the event propagation. event.preventDefault(); event.stopPropagation(); // Fetch the selection model. let model = grid.selectionModel; // Scroll by page if there is no selection model. if (!model || model.selectionMode === 'column') { grid.scrollByPage('down'); return; } // Get the normal number of cells in the page height. let n = Math.floor(grid.pageHeight / grid.defaultSizes.rowHeight); // Fetch the cursor and selection. let r = model.cursorRow; let c = model.cursorColumn; let cs = model.currentSelection(); // Set up the selection variables. let r1: number; let r2: number; let c1: number; let c2: number; let cr: number; let cc: number; let clear: SelectionModel.ClearMode; // Select or resize as needed. if (event.shiftKey) { r1 = cs ? cs.r1 : 0; r2 = cs ? cs.r2 + n : 0; c1 = cs ? cs.c1 : 0; c2 = cs ? cs.c2 : 0; cr = r; cc = c; clear = 'current'; } else { r1 = cs ? cs.r1 + n : 0; r2 = r1; c1 = c; c2 = c; cr = r1; cc = c; clear = 'all'; } // Create the new selection. model.select({ r1, c1, r2, c2, cursorRow: cr, cursorColumn: cc, clear }); // Re-fetch the current selection. cs = model.currentSelection(); // Bail if there is no selection. if (!cs) { return; } // Scroll the grid appropriately. grid.scrollToRow(cs.r2); } /** * Handle the `'Escape'` key press for the data grid. * * @param grid - The data grid of interest. * * @param event - The keyboard event of interest. */ protected onEscape(grid: DataGrid, event: KeyboardEvent): void { if (grid.selectionModel) { grid.selectionModel.clear(); } } /** * Handle the `'Delete'` key press for the data grid. * * @param grid - The data grid of interest. * * @param event - The keyboard event of interest. */ protected onDelete(grid: DataGrid, event: KeyboardEvent): void { if (grid.editable && !grid.selectionModel!.isEmpty) { const dataModel = grid.dataModel as MutableDataModel; // Fetch the max row and column. let maxRow = dataModel.rowCount('body') - 1; let maxColumn = dataModel.columnCount('body') - 1; const it = grid.selectionModel!.selections(); let s: SelectionModel.Selection | undefined; while ((s = it.next()) !== undefined) { // Clamp the cell to the model bounds. let sr1 = Math.max(0, Math.min(s.r1, maxRow)); let sc1 = Math.max(0, Math.min(s.c1, maxColumn)); let sr2 = Math.max(0, Math.min(s.r2, maxRow)); let sc2 = Math.max(0, Math.min(s.c2, maxColumn)); for (let r = sr1; r <= sr2; ++r) { for (let c = sc1; c <= sc2; ++c) { dataModel.setData('body', r, c, null); } } } } } /** * Handle the `'C'` key press for the data grid. * * @param grid - The data grid of interest. * * @param event - The keyboard event of interest. */ protected onKeyC(grid: DataGrid, event: KeyboardEvent): void { // Bail early if the modifiers aren't correct for copy. if (event.shiftKey || !Platform.accelKey(event)) { return; } // Stop the event propagation. event.preventDefault(); event.stopPropagation(); // Copy the current selection to the clipboard. grid.copyToClipboard(); } private _disposed = false; } lumino-2021.12.13/packages/datagrid/src/basicmousehandler.ts000066400000000000000000000646221415564225700236170ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2019, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { IDisposable } from '@lumino/disposable'; import { Platform } from '@lumino/domutils'; import { Drag } from '@lumino/dragdrop'; import { DataGrid } from './datagrid'; import { HyperlinkRenderer } from './hyperlinkrenderer'; import { DataModel } from './datamodel'; import { SelectionModel } from './selectionmodel'; import { CellEditor } from './celleditor'; import { CellGroup } from './cellgroup'; import { CellRenderer } from './cellrenderer'; import { TextRenderer } from './textrenderer'; /** * A basic implementation of a data grid mouse handler. * * #### Notes * This class may be subclassed and customized as needed. */ export class BasicMouseHandler implements DataGrid.IMouseHandler { /** * Dispose of the resources held by the mouse handler. */ dispose(): void { // Bail early if the handler is already disposed. if (this._disposed) { return; } // Release any held resources. this.release(); // Mark the handler as disposed. this._disposed = true; } /** * Whether the mouse handler is disposed. */ get isDisposed(): boolean { return this._disposed; } /** * Release the resources held by the handler. */ release(): void { // Bail early if the is no press data. if (!this._pressData) { return; } // Clear the autoselect timeout. if (this._pressData.type === 'select') { this._pressData.timeout = -1; } // Clear the press data. this._pressData.override.dispose(); this._pressData = null; } /** * Handle the mouse hover event for the data grid. * * @param grid - The data grid of interest. * * @param event - The mouse hover event of interest. */ onMouseHover(grid: DataGrid, event: MouseEvent): void { // Hit test the grid. let hit = grid.hitTest(event.clientX, event.clientY); // Get the resize handle for the hit test. let handle = Private.resizeHandleForHitTest(hit); // Fetch the cursor for the handle. let cursor = this.cursorForHandle(handle); // Hyperlink logic. const config = Private.createCellConfigObject(grid, hit); if (config) { // Retrieve renderer for hovered cell. const renderer = grid.cellRenderers.get(config); if (renderer instanceof HyperlinkRenderer) { cursor = this.cursorForHandle('hyperlink'); } } // Update the viewport cursor based on the part. grid.viewport.node.style.cursor = cursor; // TODO support user-defined hover items } /** * Handle the mouse leave event for the data grid. * * @param grid - The data grid of interest. * * @param event - The mouse hover event of interest. */ onMouseLeave(grid: DataGrid, event: MouseEvent): void { // TODO support user-defined hover popups. // Clear the viewport cursor. grid.viewport.node.style.cursor = ''; } /** * Handle the mouse down event for the data grid. * * @param grid - The data grid of interest. * * @param event - The mouse down event of interest. */ onMouseDown(grid: DataGrid, event: MouseEvent): void { // Unpack the event. let { clientX, clientY } = event; // Hit test the grid. let hit = grid.hitTest(clientX, clientY); // Unpack the hit test. const { region, row, column } = hit; // Bail if the hit test is on an uninteresting region. if (region === 'void') { return; } // Fetch the modifier flags. let shift = event.shiftKey; let accel = Platform.accelKey(event); // Hyperlink logic. if (grid) { // Create cell config object. const config = Private.createCellConfigObject(grid, hit); // Retrieve cell renderer. let renderer = grid.cellRenderers.get(config!); // Only process hyperlink renderers. if (renderer instanceof HyperlinkRenderer) { // Use the url param if it exists. let url = CellRenderer.resolveOption(renderer.url, config!); // Otherwise assume cell value is the URL. if (!url) { const format = TextRenderer.formatGeneric(); url = format(config!); } // Open the hyperlink only if user hit Ctrl+Click. if (accel) { window.open(url); // Reset cursor default after clicking const cursor = this.cursorForHandle('none'); grid.viewport.node.style.cursor = cursor; // Not applying selections if navigating away. return; } } } // If the hit test is the body region, the only option is select. if (region === 'body') { // Fetch the selection model. let model = grid.selectionModel; // Bail early if there is no selection model. if (!model) { return; } // Override the document cursor. let override = Drag.overrideCursor('default'); // Set up the press data. this._pressData = { type: 'select', region, row, column, override, localX: -1, localY: -1, timeout: -1 }; // Set up the selection variables. let r1: number; let c1: number; let r2: number; let c2: number; let cursorRow: number; let cursorColumn: number; let clear: SelectionModel.ClearMode; // Accel == new selection, keep old selections. if (accel) { r1 = row; r2 = row; c1 = column; c2 = column; cursorRow = row; cursorColumn = column; clear = 'none'; } else if (shift) { r1 = model.cursorRow; r2 = row; c1 = model.cursorColumn; c2 = column; cursorRow = model.cursorRow; cursorColumn = model.cursorColumn; clear = 'current'; } else { r1 = row; r2 = row; c1 = column; c2 = column; cursorRow = row; cursorColumn = column; clear = 'all'; } // Make the selection. model.select({ r1, c1, r2, c2, cursorRow, cursorColumn, clear }); // Done. return; } // Otherwise, the hit test is on a header region. // Convert the hit test into a part. let handle = Private.resizeHandleForHitTest(hit); // Fetch the cursor for the handle. let cursor = this.cursorForHandle(handle); // Handle horizontal resize. if (handle === 'left' || handle === 'right') { // Set up the resize data type. const type = 'column-resize'; // Determine the column region. let rgn: DataModel.ColumnRegion = region === 'column-header' ? 'body' : 'row-header'; // Determine the section index. let index = handle === 'left' ? column - 1 : column; // Fetch the section size. let size = grid.columnSize(rgn, index); // Override the document cursor. let override = Drag.overrideCursor(cursor); // Create the temporary press data. this._pressData = { type, region: rgn, index, size, clientX, override }; // Done. return; } // Handle vertical resize if (handle === 'top' || handle === 'bottom') { // Set up the resize data type. const type = 'row-resize'; // Determine the row region. let rgn: DataModel.RowRegion = region === 'row-header' ? 'body' : 'column-header'; // Determine the section index. let index = handle === 'top' ? row - 1 : row; // Fetch the section size. let size = grid.rowSize(rgn, index); // Override the document cursor. let override = Drag.overrideCursor(cursor); // Create the temporary press data. this._pressData = { type, region: rgn, index, size, clientY, override }; // Done. return; } // Otherwise, the only option is select. // Fetch the selection model. let model = grid.selectionModel; // Bail if there is no selection model. if (!model) { return; } // Override the document cursor. let override = Drag.overrideCursor('default'); // Set up the press data. this._pressData = { type: 'select', region, row, column, override, localX: -1, localY: -1, timeout: -1 }; // Set up the selection variables. let r1: number; let c1: number; let r2: number; let c2: number; let cursorRow: number; let cursorColumn: number; let clear: SelectionModel.ClearMode; // Compute the selection based on the pressed region. if (region === 'corner-header') { r1 = 0; r2 = Infinity; c1 = 0; c2 = Infinity; cursorRow = accel ? 0 : shift ? model.cursorRow : 0; cursorColumn = accel ? 0 : shift ? model.cursorColumn : 0; clear = accel ? 'none' : shift ? 'current' : 'all'; } else if (region === 'row-header') { r1 = accel ? row : shift ? model.cursorRow : row; r2 = row; const selectionGroup: CellGroup = { r1: r1, c1: 0, r2: r2, c2: 0 }; const joinedGroup = CellGroup.joinCellGroupsIntersectingAtAxis( grid.dataModel!, ['row-header', 'body'], 'row', selectionGroup ); // Check if there are any merges if (joinedGroup.r1 != Number.MAX_VALUE) { r1 = joinedGroup.r1; r2 = joinedGroup.r2; } c1 = 0; c2 = Infinity; cursorRow = accel ? row : shift ? model.cursorRow : row; cursorColumn = accel ? 0 : shift ? model.cursorColumn : 0; clear = accel ? 'none' : shift ? 'current' : 'all'; } else if (region === 'column-header') { r1 = 0; r2 = Infinity; c1 = accel ? column : shift ? model.cursorColumn : column; c2 = column; const selectionGroup: CellGroup = { r1: 0, c1: c1, r2: 0, c2: c2 }; const joinedGroup = CellGroup.joinCellGroupsIntersectingAtAxis( grid.dataModel!, ['column-header', 'body'], 'column', selectionGroup ); // Check if there are any merges if (joinedGroup.c1 != Number.MAX_VALUE) { c1 = joinedGroup.c1; c2 = joinedGroup.c2; } cursorRow = accel ? 0 : shift ? model.cursorRow : 0; cursorColumn = accel ? column : shift ? model.cursorColumn : column; clear = accel ? 'none' : shift ? 'current' : 'all'; } else { r1 = accel ? row : shift ? model.cursorRow : row; r2 = row; c1 = accel ? column : shift ? model.cursorColumn : column; c2 = column; cursorRow = accel ? row : shift ? model.cursorRow : row; cursorColumn = accel ? column : shift ? model.cursorColumn : column; clear = accel ? 'none' : shift ? 'current' : 'all'; } // Make the selection. model.select({ r1, c1, r2, c2, cursorRow, cursorColumn, clear }); } /** * Handle the mouse move event for the data grid. * * @param grid - The data grid of interest. * * @param event - The mouse move event of interest. */ onMouseMove(grid: DataGrid, event: MouseEvent): void { // Fetch the press data. const data = this._pressData; // Bail early if there is no press data. if (!data) { return; } // Handle a row resize. if (data.type === 'row-resize') { let dy = event.clientY - data.clientY; grid.resizeRow(data.region, data.index, data.size + dy); return; } // Handle a column resize. if (data.type === 'column-resize') { let dx = event.clientX - data.clientX; grid.resizeColumn(data.region, data.index, data.size + dx); return; } // Otherwise, it's a select. // Mouse moves during a corner header press are a no-op. if (data.region === 'corner-header') { return; } // Fetch the selection model. let model = grid.selectionModel; // Bail early if the selection model was removed. if (!model) { return; } // Map to local coordinates. let { lx, ly } = grid.mapToLocal(event.clientX, event.clientY); // Update the local mouse coordinates in the press data. data.localX = lx; data.localY = ly; // Fetch the grid geometry. let hw = grid.headerWidth; let hh = grid.headerHeight; let vpw = grid.viewportWidth; let vph = grid.viewportHeight; let sx = grid.scrollX; let sy = grid.scrollY; let msx = grid.maxScrollY; let msy = grid.maxScrollY; // Fetch the selection mode. let mode = model.selectionMode; // Set up the timeout variable. let timeout = -1; // Compute the timemout based on hit region and mouse position. if (data.region === 'row-header' || mode === 'row') { if (ly < hh && sy > 0) { timeout = Private.computeTimeout(hh - ly); } else if (ly >= vph && sy < msy) { timeout = Private.computeTimeout(ly - vph); } } else if (data.region === 'column-header' || mode === 'column') { if (lx < hw && sx > 0) { timeout = Private.computeTimeout(hw - lx); } else if (lx >= vpw && sx < msx) { timeout = Private.computeTimeout(lx - vpw); } } else { if (lx < hw && sx > 0) { timeout = Private.computeTimeout(hw - lx); } else if (lx >= vpw && sx < msx) { timeout = Private.computeTimeout(lx - vpw); } else if (ly < hh && sy > 0) { timeout = Private.computeTimeout(hh - ly); } else if (ly >= vph && sy < msy) { timeout = Private.computeTimeout(ly - vph); } } // Update or initiate the autoselect if needed. if (timeout >= 0) { if (data.timeout < 0) { data.timeout = timeout; setTimeout(() => { Private.autoselect(grid, data); }, timeout); } else { data.timeout = timeout; } return; } // Otherwise, clear the autoselect timeout. data.timeout = -1; // Map the position to virtual coordinates. let { vx, vy } = grid.mapToVirtual(event.clientX, event.clientY); // Clamp the coordinates to the limits. vx = Math.max(0, Math.min(vx, grid.bodyWidth - 1)); vy = Math.max(0, Math.min(vy, grid.bodyHeight - 1)); // Set up the selection variables. let r1: number; let c1: number; let r2: number; let c2: number; let cursorRow = model.cursorRow; let cursorColumn = model.cursorColumn; let clear: SelectionModel.ClearMode = 'current'; // Compute the selection based pressed region. if (data.region === 'row-header' || mode === 'row') { r1 = data.row; r2 = grid.rowAt('body', vy); const selectionGroup: CellGroup = { r1: r1, c1: 0, r2: r2, c2: 0 }; const joinedGroup = CellGroup.joinCellGroupsIntersectingAtAxis( grid.dataModel!, ['row-header', 'body'], 'row', selectionGroup ); // Check if there are any merges if (joinedGroup.r1 != Number.MAX_VALUE) { r1 = Math.min(r1, joinedGroup.r1); r2 = Math.max(r2, joinedGroup.r2); } c1 = 0; c2 = Infinity; } else if (data.region === 'column-header' || mode === 'column') { r1 = 0; r2 = Infinity; c1 = data.column; c2 = grid.columnAt('body', vx); const selectionGroup: CellGroup = { r1: 0, c1: c1, r2: 0, c2: c2 }; const joinedGroup = CellGroup.joinCellGroupsIntersectingAtAxis( grid.dataModel!, ['column-header', 'body'], 'column', selectionGroup ); // Check if there are any merges if (joinedGroup.c1 != Number.MAX_VALUE) { c1 = joinedGroup.c1; c2 = joinedGroup.c2; } } else { r1 = cursorRow; r2 = grid.rowAt('body', vy); c1 = cursorColumn; c2 = grid.columnAt('body', vx); } // Make the selection. model.select({ r1, c1, r2, c2, cursorRow, cursorColumn, clear }); } /** * Handle the mouse up event for the data grid. * * @param grid - The data grid of interest. * * @param event - The mouse up event of interest. */ onMouseUp(grid: DataGrid, event: MouseEvent): void { this.release(); } /** * Handle the mouse double click event for the data grid. * * @param grid - The data grid of interest. * * @param event - The mouse up event of interest. */ onMouseDoubleClick(grid: DataGrid, event: MouseEvent): void { if (!grid.dataModel) { this.release(); return; } // Unpack the event. let { clientX, clientY } = event; // Hit test the grid. let hit = grid.hitTest(clientX, clientY); // Unpack the hit test. let { region, row, column } = hit; if (region === 'void') { this.release(); return; } if (region === 'body') { if (grid.editable) { const cell: CellEditor.CellConfig = { grid: grid, row: row, column: column }; grid.editorController!.edit(cell); } } this.release(); } /** * Handle the context menu event for the data grid. * * @param grid - The data grid of interest. * * @param event - The context menu event of interest. */ onContextMenu(grid: DataGrid, event: MouseEvent): void { // TODO support user-defined context menus } /** * Handle the wheel event for the data grid. * * @param grid - The data grid of interest. * * @param event - The wheel event of interest. */ onWheel(grid: DataGrid, event: WheelEvent): void { // Bail if a mouse press is in progress. if (this._pressData) { return; } // Extract the delta X and Y movement. let dx = event.deltaX; let dy = event.deltaY; // Convert the delta values to pixel values. switch (event.deltaMode) { case 0: // DOM_DELTA_PIXEL break; case 1: // DOM_DELTA_LINE let ds = grid.defaultSizes; dx *= ds.columnWidth; dy *= ds.rowHeight; break; case 2: // DOM_DELTA_PAGE dx *= grid.pageWidth; dy *= grid.pageHeight; break; default: throw 'unreachable'; } // Scroll by the desired amount. grid.scrollBy(dx, dy); } /** * Convert a resize handle into a cursor. */ cursorForHandle(handle: ResizeHandle): string { return Private.cursorMap[handle]; } /** * Get the current pressData */ get pressData(): PressData.PressData | null { return this._pressData; } private _disposed = false; protected _pressData: PressData.PressData | null = null; } /** * A type alias for the resize handle types. */ export type ResizeHandle = | 'top' | 'left' | 'right' | 'bottom' | 'none' | 'hyperlink'; /** * The namespace for the pressdata. */ export namespace PressData { /** * A type alias for the row resize data. */ export type RowResizeData = { /** * The descriminated type for the data. */ readonly type: 'row-resize'; /** * The row region which holds the section being resized. */ readonly region: DataModel.RowRegion; /** * The index of the section being resized. */ readonly index: number; /** * The original size of the section. */ readonly size: number; /** * The original client Y position of the mouse. */ readonly clientY: number; /** * The disposable to clear the cursor override. */ readonly override: IDisposable; }; /** * A type alias for the column resize data. */ export type ColumnResizeData = { /** * The descriminated type for the data. */ readonly type: 'column-resize'; /** * The column region which holds the section being resized. */ readonly region: DataModel.ColumnRegion; /** * The index of the section being resized. */ readonly index: number; /** * The original size of the section. */ readonly size: number; /** * The original client X position of the mouse. */ readonly clientX: number; /** * The disposable to clear the cursor override. */ readonly override: IDisposable; }; /** * A type alias for the select data. */ export type SelectData = { /** * The descriminated type for the data. */ readonly type: 'select'; /** * The original region for the mouse press. */ readonly region: DataModel.CellRegion; /** * The original row that was selected. */ readonly row: number; /** * The original column that was selected. */ readonly column: number; /** * The disposable to clear the cursor override. */ readonly override: IDisposable; /** * The current local X position of the mouse. */ localX: number; /** * The current local Y position of the mouse. */ localY: number; /** * The timeout delay for the autoselect loop. */ timeout: number; }; /** * A type alias for the resize handler press data. */ export type PressData = RowResizeData | ColumnResizeData | SelectData; } /** * The namespace for the module implementation details. */ export namespace Private { /** * Creates a CellConfig object from a hit region. */ export function createCellConfigObject( grid: DataGrid, hit: DataGrid.HitTestResult ): CellRenderer.CellConfig | undefined { const { region, row, column } = hit; // Terminate call if region is void. if (region === 'void') { return undefined; } // Augment hit region params with value and metadata. const value = grid.dataModel!.data(region, row, column); const metadata = grid.dataModel!.metadata(region, row, column); // Create cell config object to retrieve cell renderer. const config = { ...hit, value: value, metadata: metadata } as CellRenderer.CellConfig; return config; } /** * Get the resize handle for a grid hit test. */ export function resizeHandleForHitTest( hit: DataGrid.HitTestResult ): ResizeHandle { // Fetch the row and column. let r = hit.row; let c = hit.column; // Fetch the leading and trailing sizes. let lw = hit.x; let lh = hit.y; let tw = hit.width - hit.x; let th = hit.height - hit.y; // Set up the result variable. let result: ResizeHandle; // Dispatch based on hit test region. switch (hit.region) { case 'corner-header': if (c > 0 && lw <= 5) { result = 'left'; } else if (tw <= 6) { result = 'right'; } else if (r > 0 && lh <= 5) { result = 'top'; } else if (th <= 6) { result = 'bottom'; } else { result = 'none'; } break; case 'column-header': if (c > 0 && lw <= 5) { result = 'left'; } else if (tw <= 6) { result = 'right'; } else if (r > 0 && lh <= 5) { result = 'top'; } else if (th <= 6) { result = 'bottom'; } else { result = 'none'; } break; case 'row-header': if (c > 0 && lw <= 5) { result = 'left'; } else if (tw <= 6) { result = 'right'; } else if (r > 0 && lh <= 5) { result = 'top'; } else if (th <= 6) { result = 'bottom'; } else { result = 'none'; } break; case 'body': result = 'none'; break; case 'void': result = 'none'; break; default: throw 'unreachable'; } // Return the result. return result; } /** * A timer callback for the autoselect loop. * * @param grid - The datagrid of interest. * * @param data - The select data of interest. */ export function autoselect(grid: DataGrid, data: PressData.SelectData): void { // Bail early if the timeout has been reset. if (data.timeout < 0) { return; } // Fetch the selection model. let model = grid.selectionModel; // Bail early if the selection model has been removed. if (!model) { return; } // Fetch the current selection. let cs = model.currentSelection(); // Bail early if there is no current selection. if (!cs) { return; } // Fetch local X and Y coordinates of the mouse. let lx = data.localX; let ly = data.localY; // Set up the selection variables. let r1 = cs.r1; let c1 = cs.c1; let r2 = cs.r2; let c2 = cs.c2; let cursorRow = model.cursorRow; let cursorColumn = model.cursorColumn; let clear: SelectionModel.ClearMode = 'current'; // Fetch the grid geometry. let hw = grid.headerWidth; let hh = grid.headerHeight; let vpw = grid.viewportWidth; let vph = grid.viewportHeight; // Fetch the selection mode. let mode = model.selectionMode; // Update the selection based on the hit region. if (data.region === 'row-header' || mode === 'row') { r2 += ly <= hh ? -1 : ly >= vph ? 1 : 0; } else if (data.region === 'column-header' || mode === 'column') { c2 += lx <= hw ? -1 : lx >= vpw ? 1 : 0; } else { r2 += ly <= hh ? -1 : ly >= vph ? 1 : 0; c2 += lx <= hw ? -1 : lx >= vpw ? 1 : 0; } // Update the current selection. model.select({ r1, c1, r2, c2, cursorRow, cursorColumn, clear }); // Re-fetch the current selection. cs = model.currentSelection(); // Bail if there is no selection. if (!cs) { return; } // Scroll the grid based on the hit region. if (data.region === 'row-header' || mode === 'row') { grid.scrollToRow(cs.r2); } else if (data.region === 'column-header' || mode == 'column') { grid.scrollToColumn(cs.c2); } else if (mode === 'cell') { grid.scrollToCell(cs.r2, cs.c2); } // Schedule the next call with the current timeout. setTimeout(() => { autoselect(grid, data); }, data.timeout); } /** * Compute the scroll timeout for the given delta distance. * * @param delta - The delta pixels from the origin. * * @returns The scaled timeout in milliseconds. */ export function computeTimeout(delta: number): number { return 5 + 120 * (1 - Math.min(128, Math.abs(delta)) / 128); } /** * A mapping of resize handle to cursor. */ export const cursorMap = { top: 'ns-resize', left: 'ew-resize', right: 'ew-resize', bottom: 'ns-resize', hyperlink: 'pointer', none: 'default' }; } lumino-2021.12.13/packages/datagrid/src/basicselectionmodel.ts000066400000000000000000000230021415564225700241220ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2019, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { IIterator, iter } from '@lumino/algorithm'; import { DataModel } from './datamodel'; import { SelectionModel } from './selectionmodel'; /** * A basic selection model implementation. * * #### Notes * This selection model is sufficient for most use cases where * structural knowledge of the data source is *not* required. */ export class BasicSelectionModel extends SelectionModel { /** * Whether the selection model is empty. */ get isEmpty(): boolean { return this._selections.length === 0; } /** * The row index of the cursor. */ get cursorRow(): number { return this._cursorRow; } /** * The column index of the cursor. */ get cursorColumn(): number { return this._cursorColumn; } /** * Move cursor down/up/left/right while making sure it remains * within the bounds of selected rectangles * * @param direction - The direction of the movement. */ moveCursorWithinSelections( direction: SelectionModel.CursorMoveDirection ): void { // Bail early if there are no selections or no existing cursor if (this.isEmpty || this.cursorRow === -1 || this._cursorColumn === -1) { return; } // Bail early if only single cell is selected const firstSelection = this._selections[0]; if ( this._selections.length === 1 && firstSelection.r1 === firstSelection.r2 && firstSelection.c1 === firstSelection.c2 ) { return; } // start from last selection rectangle if (this._cursorRectIndex === -1) { this._cursorRectIndex = this._selections.length - 1; } let cursorRect = this._selections[this._cursorRectIndex]; const dr = direction === 'down' ? 1 : direction === 'up' ? -1 : 0; const dc = direction === 'right' ? 1 : direction === 'left' ? -1 : 0; let newRow = this._cursorRow + dr; let newColumn = this._cursorColumn + dc; const r1 = Math.min(cursorRect.r1, cursorRect.r2); const r2 = Math.max(cursorRect.r1, cursorRect.r2); const c1 = Math.min(cursorRect.c1, cursorRect.c2); const c2 = Math.max(cursorRect.c1, cursorRect.c2); const moveToNextRect = () => { this._cursorRectIndex = (this._cursorRectIndex + 1) % this._selections.length; cursorRect = this._selections[this._cursorRectIndex]; newRow = Math.min(cursorRect.r1, cursorRect.r2); newColumn = Math.min(cursorRect.c1, cursorRect.c2); }; const moveToPreviousRect = () => { this._cursorRectIndex = this._cursorRectIndex === 0 ? this._selections.length - 1 : this._cursorRectIndex - 1; cursorRect = this._selections[this._cursorRectIndex]; newRow = Math.max(cursorRect.r1, cursorRect.r2); newColumn = Math.max(cursorRect.c1, cursorRect.c2); }; if (newRow > r2) { newRow = r1; newColumn += 1; if (newColumn > c2) { moveToNextRect(); } } else if (newRow < r1) { newRow = r2; newColumn -= 1; if (newColumn < c1) { moveToPreviousRect(); } } else if (newColumn > c2) { newColumn = c1; newRow += 1; if (newRow > r2) { moveToNextRect(); } } else if (newColumn < c1) { newColumn = c2; newRow -= 1; if (newRow < r1) { moveToPreviousRect(); } } this._cursorRow = newRow; this._cursorColumn = newColumn; // Emit the changed signal. this.emitChanged(); } /** * Get the current selection in the selection model. * * @returns The current selection or `null`. * * #### Notes * This is the selection which holds the cursor. */ currentSelection(): SelectionModel.Selection | null { return this._selections[this._selections.length - 1] || null; } /** * Get an iterator of the selections in the model. * * @returns A new iterator of the current selections. * * #### Notes * The data grid will render the selections in order. */ selections(): IIterator { return iter(this._selections); } /** * Select the specified cells. * * @param args - The arguments for the selection. */ select(args: SelectionModel.SelectArgs): void { // Fetch the current row and column counts; let rowCount = this.dataModel.rowCount('body'); let columnCount = this.dataModel.columnCount('body'); // Bail early if there is no content. if (rowCount <= 0 || columnCount <= 0) { return; } // Unpack the arguments. let { r1, c1, r2, c2, cursorRow, cursorColumn, clear } = args; // Clear the necessary selections. if (clear === 'all') { this._selections.length = 0; } else if (clear === 'current') { this._selections.pop(); } // Clamp to the data model bounds. r1 = Math.max(0, Math.min(r1, rowCount - 1)); r2 = Math.max(0, Math.min(r2, rowCount - 1)); c1 = Math.max(0, Math.min(c1, columnCount - 1)); c2 = Math.max(0, Math.min(c2, columnCount - 1)); // Indicate if a row/column has already been selected. let alreadySelected = false; // Handle the selection mode. if (this.selectionMode === 'row') { c1 = 0; c2 = columnCount - 1; alreadySelected = this._selections.filter(selection => selection.r1 === r1).length !== 0; // Remove from selections if already selected. this._selections = alreadySelected ? this._selections.filter(selection => selection.r1 !== r1) : this._selections; } else if (this.selectionMode === 'column') { r1 = 0; r2 = rowCount - 1; alreadySelected = this._selections.filter(selection => selection.c1 === c1).length !== 0; // Remove from selections if already selected. this._selections = alreadySelected ? this._selections.filter(selection => selection.c1 !== c1) : this._selections; } // Alias the cursor row and column. let cr = cursorRow; let cc = cursorColumn; // Compute the new cursor location. if (cr < 0 || (cr < r1 && cr < r2) || (cr > r1 && cr > r2)) { cr = r1; } if (cc < 0 || (cc < c1 && cc < c2) || (cc > c1 && cc > c2)) { cc = c1; } // Update the cursor. this._cursorRow = cr; this._cursorColumn = cc; this._cursorRectIndex = this._selections.length; // Add the new selection if it wasn't already selected. if (!alreadySelected) { this._selections.push({ r1, c1, r2, c2 }); } // Emit the changed signal. this.emitChanged(); } /** * Clear all selections in the selection model. */ clear(): void { // Bail early if there are no selections. if (this._selections.length === 0) { return; } // Reset the internal state. this._cursorRow = -1; this._cursorColumn = -1; this._cursorRectIndex = -1; this._selections.length = 0; // Emit the changed signal. this.emitChanged(); } /** * A signal handler for the data model `changed` signal. * * @param args - The arguments for the signal. */ protected onDataModelChanged( sender: DataModel, args: DataModel.ChangedArgs ): void { // Bail early if the model has no current selections. if (this._selections.length === 0) { return; } // Bail early if the cells have changed in place. if (args.type === 'cells-changed') { return; } // Bail early if there is no change to the row or column count. if (args.type === 'rows-moved' || args.type === 'columns-moved') { return; } // Fetch the last row and column index. let lr = sender.rowCount('body') - 1; let lc = sender.columnCount('body') - 1; // Bail early if the data model is empty. if (lr < 0 || lc < 0) { this._selections.length = 0; this.emitChanged(); return; } // Fetch the selection mode. let mode = this.selectionMode; // Set up the assignment index variable. let j = 0; // Iterate over the current selections. for (let i = 0, n = this._selections.length; i < n; ++i) { // Unpack the selection. let { r1, c1, r2, c2 } = this._selections[i]; // Skip the selection if it will disappear. if ((lr < r1 && lr < r2) || (lc < c1 && lc < c2)) { continue; } // Modify the bounds based on the selection mode. if (mode === 'row') { r1 = Math.max(0, Math.min(r1, lr)); r2 = Math.max(0, Math.min(r2, lr)); c1 = 0; c2 = lc; } else if (mode === 'column') { r1 = 0; r2 = lr; c1 = Math.max(0, Math.min(c1, lc)); c2 = Math.max(0, Math.min(c2, lc)); } else { r1 = Math.max(0, Math.min(r1, lr)); r2 = Math.max(0, Math.min(r2, lr)); c1 = Math.max(0, Math.min(c1, lc)); c2 = Math.max(0, Math.min(c2, lc)); } // Assign the modified selection to the array. this._selections[j++] = { r1, c1, r2, c2 }; } // Remove the stale selections. this._selections.length = j; // Emit the changed signal. this.emitChanged(); } private _cursorRow = -1; private _cursorColumn = -1; private _cursorRectIndex = -1; private _selections: SelectionModel.Selection[] = []; } lumino-2021.12.13/packages/datagrid/src/celleditor.ts000066400000000000000000001136731415564225700222560ustar00rootroot00000000000000/*----------------------------------------------------------------------------- | Copyright (c) 2014-2019, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { IDisposable } from '@lumino/disposable'; import { DataGrid } from './datagrid'; import { SelectionModel } from './selectionmodel'; import { getKeyboardLayout } from '@lumino/keyboard'; import { Signal } from '@lumino/signaling'; import { Notification } from './notification'; import { CellGroup } from './cellgroup'; /** * A response object returned from cell input validator */ export interface ICellInputValidatorResponse { /** * Flag indicating cell input is valid or not */ valid: boolean; /** * Validation error message. Set only when input is invalid */ message?: string; } /** * An object which validates cell input values. */ export interface ICellInputValidator { /** * Validate cell input. * * @param cell - The object holding cell configuration data. * * @param value - The cell value input. * * @returns An object with validation result. */ validate( cell: CellEditor.CellConfig, value: any ): ICellInputValidatorResponse; } /** * An object returned from cell editor after a successful edit. */ export interface ICellEditResponse { /** * An object which holds the configuration data for a cell. */ cell: CellEditor.CellConfig; /** * Value input. */ value: any; /** * Cursor move direction based on keys pressed to end the edit. */ cursorMovement: SelectionModel.CursorMoveDirection; } /** * An object implementing cell editing. */ export interface ICellEditor { /** * Start editing the cell. * * @param cell - The object holding cell configuration data. * * @param options - The cell editing options. */ edit(cell: CellEditor.CellConfig, options?: ICellEditOptions): void; /** * Cancel editing the cell. */ cancel(): void; } // default validation error message const DEFAULT_INVALID_INPUT_MESSAGE = 'Invalid input!'; // A type alias for available cell data types export type CellDataType = | 'string' | 'number' | 'integer' | 'boolean' | 'date' | 'string:option' | 'number:option' | 'integer:option' | 'date:option' | 'string:dynamic-option' | 'number:dynamic-option' | 'integer:dynamic-option' | 'date:dynamic-option'; /** * An object containing cell editing options. */ export interface ICellEditOptions { /** * Cell editor to use for editing. * * #### Notes * This object is only used by cell editor controller. * If not set, controller picks the most suitable editor * for the particular cell configuration. */ editor?: ICellEditor; /** * Cell input validator to use for value validation. */ validator?: ICellInputValidator; /** * Callback method to call on cell edit commit. */ onCommit?: (response: ICellEditResponse) => void; /** * Callback method to call on cell edit cancel. */ onCancel?: () => void; } /** * A cell input validator object which always returns valid. */ export class PassInputValidator implements ICellInputValidator { /** * Validate cell input. * * @param cell - The object holding cell configuration data. * * @param value - The cell value input. * * @returns An object with validation result. */ validate( cell: CellEditor.CellConfig, value: any ): ICellInputValidatorResponse { return { valid: true }; } } /** * Text cell input validator. */ export class TextInputValidator implements ICellInputValidator { /** * Validate cell input. * * @param cell - The object holding cell configuration data. * * @param value - The cell value input. * * @returns An object with validation result. */ validate( cell: CellEditor.CellConfig, value: string ): ICellInputValidatorResponse { if (value === null) { return { valid: true }; } if (typeof value !== 'string') { return { valid: false, message: 'Input must be valid text' }; } if (!isNaN(this.minLength) && value.length < this.minLength) { return { valid: false, message: `Text length must be greater than ${this.minLength}` }; } if (!isNaN(this.maxLength) && value.length > this.maxLength) { return { valid: false, message: `Text length must be less than ${this.maxLength}` }; } if (this.pattern && !this.pattern.test(value)) { return { valid: false, message: `Text doesn't match the required pattern` }; } return { valid: true }; } /** * Minimum text length * * The default is Number.NaN, meaning no minimum constraint */ minLength: number = Number.NaN; /** * Maximum text length * * The default is Number.NaN, meaning no maximum constraint */ maxLength: number = Number.NaN; /** * Required text pattern as regular expression * * The default is null, meaning no pattern constraint */ pattern: RegExp | null = null; } /** * Integer cell input validator. */ export class IntegerInputValidator implements ICellInputValidator { /** * Validate cell input. * * @param cell - The object holding cell configuration data. * * @param value - The cell value input. * * @returns An object with validation result. */ validate( cell: CellEditor.CellConfig, value: number ): ICellInputValidatorResponse { if (value === null) { return { valid: true }; } if (isNaN(value) || value % 1 !== 0) { return { valid: false, message: 'Input must be valid integer' }; } if (!isNaN(this.min) && value < this.min) { return { valid: false, message: `Input must be greater than ${this.min}` }; } if (!isNaN(this.max) && value > this.max) { return { valid: false, message: `Input must be less than ${this.max}` }; } return { valid: true }; } /** * Minimum value * * The default is Number.NaN, meaning no minimum constraint */ min: number = Number.NaN; /** * Maximum value * * The default is Number.NaN, meaning no maximum constraint */ max: number = Number.NaN; } /** * Real number cell input validator. */ export class NumberInputValidator implements ICellInputValidator { /** * Validate cell input. * * @param cell - The object holding cell configuration data. * * @param value - The cell value input. * * @returns An object with validation result. */ validate( cell: CellEditor.CellConfig, value: number ): ICellInputValidatorResponse { if (value === null) { return { valid: true }; } if (isNaN(value)) { return { valid: false, message: 'Input must be valid number' }; } if (!isNaN(this.min) && value < this.min) { return { valid: false, message: `Input must be greater than ${this.min}` }; } if (!isNaN(this.max) && value > this.max) { return { valid: false, message: `Input must be less than ${this.max}` }; } return { valid: true }; } /** * Minimum value * * The default is Number.NaN, meaning no minimum constraint */ min: number = Number.NaN; /** * Maximum value * * The default is Number.NaN, meaning no maximum constraint */ max: number = Number.NaN; } /** * An abstract base class that provides the most of the functionality * needed by a cell editor. All of the built-in cell editors * for various cell types are derived from this base class. Custom cell editors * can be easily implemented by extending this class. */ export abstract class CellEditor implements ICellEditor, IDisposable { /** * Construct a new cell editor. */ constructor() { this.inputChanged.connect(() => { this.validate(); }); } /** * Whether the cell editor is disposed. */ get isDisposed(): boolean { return this._disposed; } /** * Dispose of the resources held by cell editor. */ dispose(): void { if (this._disposed) { return; } if (this._gridWheelEventHandler) { this.cell.grid.node.removeEventListener( 'wheel', this._gridWheelEventHandler ); this._gridWheelEventHandler = null; } this._closeValidityNotification(); this._disposed = true; this.cell.grid.node.removeChild(this.viewportOccluder); } /** * Start editing the cell. * * @param cell - The object holding cell configuration data. * * @param options - The cell editing options. */ edit(cell: CellEditor.CellConfig, options?: ICellEditOptions): void { this.cell = cell; this.onCommit = options && options.onCommit; this.onCancel = options && options.onCancel; this.validator = options && options.validator ? options.validator : this.createValidatorBasedOnType(); this._gridWheelEventHandler = () => { this._closeValidityNotification(); this.updatePosition(); }; cell.grid.node.addEventListener('wheel', this._gridWheelEventHandler); this._addContainer(); this.updatePosition(); this.startEditing(); } /** * Cancel editing the cell. */ cancel() { if (this._disposed) { return; } this.dispose(); if (this.onCancel) { this.onCancel(); } } /** * Start editing the cell. Usually an editor widget is created and * added to `editorContainer` */ protected abstract startEditing(): void; /** * Return the current input entered. This method throws exceptions * if input is invalid. Error message in exception is shown as notification. */ protected abstract getInput(): any; /** * Whether the value input is valid. */ protected get validInput(): boolean { return this._validInput; } /** * Validate the cell input. Shows validation error notification when input is invalid. */ protected validate() { let value; try { value = this.getInput(); } catch (error) { console.log(`Input error: ${error.message}`); this.setValidity(false, error.message || DEFAULT_INVALID_INPUT_MESSAGE); return; } if (this.validator) { const result = this.validator.validate(this.cell, value); if (result.valid) { this.setValidity(true); } else { this.setValidity( false, result.message || DEFAULT_INVALID_INPUT_MESSAGE ); } } else { this.setValidity(true); } } /** * Set validity flag. * * @param valid - Whether the input is valid. * * @param message - Notification message to show. * * If message is set to empty string (which is the default) * existing notification popup is removed if any. */ protected setValidity(valid: boolean, message: string = '') { this._validInput = valid; this._closeValidityNotification(); if (valid) { this.editorContainer.classList.remove('lm-mod-invalid'); } else { this.editorContainer.classList.add('lm-mod-invalid'); // show a notification popup if (message !== '') { this.validityNotification = new Notification({ target: this.editorContainer, message: message, placement: 'bottom', timeout: 5000 }); this.validityNotification.show(); } } } /** * Create and return a cell input validator based on configuration of the * cell being edited. If no suitable validator can be found, it returns undefined. */ protected createValidatorBasedOnType(): ICellInputValidator | undefined { const cell = this.cell; const metadata = cell.grid.dataModel!.metadata( 'body', cell.row, cell.column ); switch (metadata && metadata.type) { case 'string': { const validator = new TextInputValidator(); if (typeof metadata!.format === 'string') { const format = metadata!.format; switch (format) { case 'email': validator.pattern = new RegExp( '^([a-z0-9_.-]+)@([da-z.-]+).([a-z.]{2,6})$' ); break; case 'uuid': validator.pattern = new RegExp( '[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}' ); break; case 'uri': // TODO break; case 'binary': // TODO break; } } if (metadata!.constraint) { if (metadata!.constraint.minLength !== undefined) { validator.minLength = metadata!.constraint.minLength; } if (metadata!.constraint.maxLength !== undefined) { validator.maxLength = metadata!.constraint.maxLength; } if (typeof metadata!.constraint.pattern === 'string') { validator.pattern = new RegExp(metadata!.constraint.pattern); } } return validator; } break; case 'number': { const validator = new NumberInputValidator(); if (metadata!.constraint) { if (metadata!.constraint.minimum !== undefined) { validator.min = metadata!.constraint.minimum; } if (metadata!.constraint.maximum !== undefined) { validator.max = metadata!.constraint.maximum; } } return validator; } break; case 'integer': { const validator = new IntegerInputValidator(); if (metadata!.constraint) { if (metadata!.constraint.minimum !== undefined) { validator.min = metadata!.constraint.minimum; } if (metadata!.constraint.maximum !== undefined) { validator.max = metadata!.constraint.maximum; } } return validator; } break; } return undefined; } /** * Compute cell rectangle and return with other cell properties. */ protected getCellInfo(cell: CellEditor.CellConfig): Private.ICellInfo { const { grid, row, column } = cell; let data, columnX, rowY, width, height; const cellGroup = CellGroup.getGroup(grid.dataModel!, 'body', row, column); if (cellGroup) { columnX = grid.headerWidth - grid.scrollX + grid.columnOffset('body', cellGroup.c1); rowY = grid.headerHeight - grid.scrollY + grid.rowOffset('body', cellGroup.r1); width = 0; height = 0; for (let r = cellGroup.r1; r <= cellGroup.r2; r++) { height += grid.rowSize('body', r); } for (let c = cellGroup.c1; c <= cellGroup.c2; c++) { width += grid.columnSize('body', c); } data = grid.dataModel!.data('body', cellGroup.r1, cellGroup.c1); } else { columnX = grid.headerWidth - grid.scrollX + grid.columnOffset('body', column); rowY = grid.headerHeight - grid.scrollY + grid.rowOffset('body', row); width = grid.columnSize('body', column); height = grid.rowSize('body', row); data = grid.dataModel!.data('body', row, column); } return { grid: grid, row: row, column: column, data: data, x: columnX, y: rowY, width: width, height: height }; } /** * Reposition cell editor by moving viewport occluder and cell editor container. */ protected updatePosition(): void { const grid = this.cell.grid; const cellInfo = this.getCellInfo(this.cell); const headerHeight = grid.headerHeight; const headerWidth = grid.headerWidth; this.viewportOccluder.style.top = headerHeight + 'px'; this.viewportOccluder.style.left = headerWidth + 'px'; this.viewportOccluder.style.width = grid.viewportWidth - headerWidth + 'px'; this.viewportOccluder.style.height = grid.viewportHeight - headerHeight + 'px'; this.viewportOccluder.style.position = 'absolute'; this.editorContainer.style.left = cellInfo.x - 1 - headerWidth + 'px'; this.editorContainer.style.top = cellInfo.y - 1 - headerHeight + 'px'; this.editorContainer.style.width = cellInfo.width + 1 + 'px'; this.editorContainer.style.height = cellInfo.height + 1 + 'px'; this.editorContainer.style.visibility = 'visible'; this.editorContainer.style.position = 'absolute'; } /** * Commit the edited value. * * @param cursorMovement - Cursor move direction based on keys pressed to end the edit. * * @returns true on valid input, false otherwise. */ protected commit( cursorMovement: SelectionModel.CursorMoveDirection = 'none' ): boolean { this.validate(); if (!this._validInput) { return false; } let value; try { value = this.getInput(); } catch (error) { console.log(`Input error: ${error.message}`); return false; } this.dispose(); if (this.onCommit) { this.onCommit({ cell: this.cell, value: value, cursorMovement: cursorMovement }); } return true; } /** * Create container elements needed to prevent editor widget overflow * beyond viewport and to position cell editor widget. */ private _addContainer() { this.viewportOccluder = document.createElement('div'); this.viewportOccluder.className = 'lm-DataGrid-cellEditorOccluder'; this.cell.grid.node.appendChild(this.viewportOccluder); this.editorContainer = document.createElement('div'); this.editorContainer.className = 'lm-DataGrid-cellEditorContainer'; this.viewportOccluder.appendChild(this.editorContainer); // update mouse event pass-through state based on input validity this.editorContainer.addEventListener('mouseleave', (event: MouseEvent) => { this.viewportOccluder.style.pointerEvents = this._validInput ? 'none' : 'auto'; }); this.editorContainer.addEventListener('mouseenter', (event: MouseEvent) => { this.viewportOccluder.style.pointerEvents = 'none'; }); } /** * Remove validity notification popup. */ private _closeValidityNotification() { if (this.validityNotification) { this.validityNotification.close(); this.validityNotification = null; } } /** * A signal emitted when input changes. */ protected inputChanged = new Signal(this); /** * Callback method to call on cell edit commit. */ protected onCommit?: (response: ICellEditResponse) => void; /** * Callback method to call on cell edit cancel. */ protected onCancel?: () => void; /** * Cell configuration data for the cell being edited. */ protected cell: CellEditor.CellConfig; /** * Cell input validator to use for the cell being edited. */ protected validator: ICellInputValidator | undefined; /** * The div element used to prevent editor widget overflow beyond grid viewport. */ protected viewportOccluder: HTMLDivElement; /** * The div element used to contain and position editor widget. */ protected editorContainer: HTMLDivElement; /** * Notification popup used to show validation error messages. */ protected validityNotification: Notification | null = null; /** * Whether the cell editor is disposed. */ private _disposed = false; /** * Whether the value input is valid. */ private _validInput: boolean = true; /** * Grid wheel event handler. */ private _gridWheelEventHandler: | ((this: HTMLElement, ev: WheelEvent) => any) | null = null; } /** * Abstract base class with shared functionality * for cell editors which use HTML Input widget as editor. */ export abstract class InputCellEditor extends CellEditor { /** * Handle the DOM events for the editor. * * @param event - The DOM event sent to the editor. */ handleEvent(event: Event): void { switch (event.type) { case 'keydown': this._onKeyDown(event as KeyboardEvent); break; case 'blur': this._onBlur(event as FocusEvent); break; case 'input': this._onInput(event); break; } } /** * Dispose of the resources held by cell editor. */ dispose() { if (this.isDisposed) { return; } this._unbindEvents(); super.dispose(); } /** * Start editing the cell. */ protected startEditing() { this.createWidget(); const cell = this.cell; const cellInfo = this.getCellInfo(cell); this.input.value = this.deserialize(cellInfo.data); this.editorContainer.appendChild(this.input); this.input.focus(); this.input.select(); this.bindEvents(); } protected deserialize(value: any): any { if (value === null || value === undefined) { return ''; } return value.toString(); } protected createWidget() { const input = document.createElement('input'); input.classList.add('lm-DataGrid-cellEditorWidget'); input.classList.add('lm-DataGrid-cellEditorInput'); input.spellcheck = false; input.type = this.inputType; this.input = input; } protected bindEvents() { this.input.addEventListener('keydown', this); this.input.addEventListener('blur', this); this.input.addEventListener('input', this); } private _unbindEvents() { this.input.removeEventListener('keydown', this); this.input.removeEventListener('blur', this); this.input.removeEventListener('input', this); } private _onKeyDown(event: KeyboardEvent) { switch (getKeyboardLayout().keyForKeydownEvent(event)) { case 'Enter': this.commit(event.shiftKey ? 'up' : 'down'); break; case 'Tab': this.commit(event.shiftKey ? 'left' : 'right'); event.stopPropagation(); event.preventDefault(); break; case 'Escape': this.cancel(); break; default: break; } } private _onBlur(event: FocusEvent) { if (this.isDisposed) { return; } if (!this.commit()) { event.preventDefault(); event.stopPropagation(); this.input.focus(); } } private _onInput(event: Event) { this.inputChanged.emit(void 0); } protected input: HTMLInputElement; protected abstract inputType: string; } /** * Cell editor for text cells. */ export class TextCellEditor extends InputCellEditor { /** * Return the current text input entered. */ protected getInput(): string | null { return this.input.value; } protected inputType: string = 'text'; } /** * Cell editor for real number cells. */ export class NumberCellEditor extends InputCellEditor { /** * Start editing the cell. */ protected startEditing() { super.startEditing(); this.input.step = 'any'; const cell = this.cell; const metadata = cell.grid.dataModel!.metadata( 'body', cell.row, cell.column ); const constraint = metadata.constraint; if (constraint) { if (constraint.minimum) { this.input.min = constraint.minimum; } if (constraint.maximum) { this.input.max = constraint.maximum; } } } /** * Return the current number input entered. This method throws exception * if input is invalid. */ protected getInput(): number | null { let value = this.input.value; if (value.trim() === '') { return null; } const floatValue = parseFloat(value); if (isNaN(floatValue)) { throw new Error('Invalid input'); } return floatValue; } protected inputType: string = 'number'; } /** * Cell editor for integer cells. */ export class IntegerCellEditor extends InputCellEditor { /** * Start editing the cell. */ protected startEditing() { super.startEditing(); this.input.step = '1'; const cell = this.cell; const metadata = cell.grid.dataModel!.metadata( 'body', cell.row, cell.column ); const constraint = metadata.constraint; if (constraint) { if (constraint.minimum) { this.input.min = constraint.minimum; } if (constraint.maximum) { this.input.max = constraint.maximum; } } } /** * Return the current integer input entered. This method throws exception * if input is invalid. */ protected getInput(): number | null { let value = this.input.value; if (value.trim() === '') { return null; } let intValue = parseInt(value); if (isNaN(intValue)) { throw new Error('Invalid input'); } return intValue; } protected inputType: string = 'number'; } /** * Cell editor for date cells. */ export class DateCellEditor extends CellEditor { /** * Handle the DOM events for the editor. * * @param event - The DOM event sent to the editor. */ handleEvent(event: Event): void { switch (event.type) { case 'keydown': this._onKeyDown(event as KeyboardEvent); break; case 'blur': this._onBlur(event as FocusEvent); break; } } /** * Dispose of the resources held by cell editor. */ dispose() { if (this.isDisposed) { return; } this._unbindEvents(); super.dispose(); } /** * Start editing the cell. */ protected startEditing() { this._createWidget(); const cell = this.cell; const cellInfo = this.getCellInfo(cell); this._input.value = this._deserialize(cellInfo.data); this.editorContainer.appendChild(this._input); this._input.focus(); this._bindEvents(); } /** * Return the current date input entered. */ protected getInput(): string | null { return this._input.value; } private _deserialize(value: any): any { if (value === null || value === undefined) { return ''; } return value.toString(); } private _createWidget() { const input = document.createElement('input'); input.type = 'date'; input.pattern = 'd{4}-d{2}-d{2}'; input.classList.add('lm-DataGrid-cellEditorWidget'); input.classList.add('lm-DataGrid-cellEditorInput'); this._input = input; } private _bindEvents() { this._input.addEventListener('keydown', this); this._input.addEventListener('blur', this); } private _unbindEvents() { this._input.removeEventListener('keydown', this); this._input.removeEventListener('blur', this); } private _onKeyDown(event: KeyboardEvent) { switch (getKeyboardLayout().keyForKeydownEvent(event)) { case 'Enter': this.commit(event.shiftKey ? 'up' : 'down'); break; case 'Tab': this.commit(event.shiftKey ? 'left' : 'right'); event.stopPropagation(); event.preventDefault(); break; case 'Escape': this.cancel(); break; default: break; } } private _onBlur(event: FocusEvent) { if (this.isDisposed) { return; } if (!this.commit()) { event.preventDefault(); event.stopPropagation(); this._input.focus(); } } private _input: HTMLInputElement; } /** * Cell editor for boolean cells. */ export class BooleanCellEditor extends CellEditor { /** * Handle the DOM events for the editor. * * @param event - The DOM event sent to the editor. */ handleEvent(event: Event): void { switch (event.type) { case 'keydown': this._onKeyDown(event as KeyboardEvent); break; case 'mousedown': // fix focus loss problem in Safari and Firefox this._input.focus(); event.stopPropagation(); event.preventDefault(); break; case 'blur': this._onBlur(event as FocusEvent); break; } } /** * Dispose of the resources held by cell editor. */ dispose() { if (this.isDisposed) { return; } this._unbindEvents(); super.dispose(); } /** * Start editing the cell. */ protected startEditing() { this._createWidget(); const cell = this.cell; const cellInfo = this.getCellInfo(cell); this._input.checked = this._deserialize(cellInfo.data); this.editorContainer.appendChild(this._input); this._input.focus(); this._bindEvents(); } /** * Return the current boolean input entered. */ protected getInput(): boolean | null { return this._input.checked; } private _deserialize(value: any): any { if (value === null || value === undefined) { return false; } return value == true; } private _createWidget() { const input = document.createElement('input'); input.classList.add('lm-DataGrid-cellEditorWidget'); input.classList.add('lm-DataGrid-cellEditorCheckbox'); input.type = 'checkbox'; input.spellcheck = false; this._input = input; } private _bindEvents() { this._input.addEventListener('keydown', this); this._input.addEventListener('mousedown', this); this._input.addEventListener('blur', this); } private _unbindEvents() { this._input.removeEventListener('keydown', this); this._input.removeEventListener('mousedown', this); this._input.removeEventListener('blur', this); } private _onKeyDown(event: KeyboardEvent) { switch (getKeyboardLayout().keyForKeydownEvent(event)) { case 'Enter': this.commit(event.shiftKey ? 'up' : 'down'); break; case 'Tab': this.commit(event.shiftKey ? 'left' : 'right'); event.stopPropagation(); event.preventDefault(); break; case 'Escape': this.cancel(); break; default: break; } } private _onBlur(event: FocusEvent) { if (this.isDisposed) { return; } if (!this.commit()) { event.preventDefault(); event.stopPropagation(); this._input.focus(); } } private _input: HTMLInputElement; } /** * Cell editor for option cells. * * It supports multiple option selection. If cell metadata contains * type attribute 'array', then it behaves as a multi select. * In that case cell data is expected to be list of string values. */ export class OptionCellEditor extends CellEditor { /** * Dispose of the resources held by cell editor. */ dispose() { if (this.isDisposed) { return; } super.dispose(); if (this._isMultiSelect) { document.body.removeChild(this._select); } } /** * Start editing the cell. */ protected startEditing() { const cell = this.cell; const cellInfo = this.getCellInfo(cell); const metadata = cell.grid.dataModel!.metadata( 'body', cell.row, cell.column ); this._isMultiSelect = metadata.type === 'array'; this._createWidget(); if (this._isMultiSelect) { this._select.multiple = true; const values = this._deserialize(cellInfo.data) as string[]; for (let i = 0; i < this._select.options.length; ++i) { const option = this._select.options.item(i); option!.selected = values.indexOf(option!.value) !== -1; } document.body.appendChild(this._select); } else { this._select.value = this._deserialize(cellInfo.data) as string; this.editorContainer.appendChild(this._select); } this._select.focus(); this._bindEvents(); this.updatePosition(); } /** * Return the current option input. */ protected getInput(): string | string[] | null { if (this._isMultiSelect) { const input: string[] = []; for (let i = 0; i < this._select.selectedOptions.length; ++i) { input.push(this._select.selectedOptions.item(i)!.value); } return input; } else { return this._select.value; } } /** * Reposition cell editor. */ protected updatePosition(): void { super.updatePosition(); if (!this._isMultiSelect) { return; } const cellInfo = this.getCellInfo(this.cell); this._select.style.position = 'absolute'; const editorContainerRect = this.editorContainer.getBoundingClientRect(); this._select.style.left = editorContainerRect.left + 'px'; this._select.style.top = editorContainerRect.top + cellInfo.height + 'px'; this._select.style.width = editorContainerRect.width + 'px'; this._select.style.maxHeight = '60px'; this.editorContainer.style.visibility = 'hidden'; } private _deserialize(value: any): string | string[] { if (value === null || value === undefined) { return ''; } if (this._isMultiSelect) { const values: string[] = []; if (Array.isArray(value)) { for (let item of value) { values.push(item.toString()); } } return values; } else { return value.toString(); } } private _createWidget() { const cell = this.cell; const metadata = cell.grid.dataModel!.metadata( 'body', cell.row, cell.column ); const items = metadata.constraint.enum; const select = document.createElement('select'); select.classList.add('lm-DataGrid-cellEditorWidget'); for (let item of items) { const option = document.createElement('option'); option.value = item; option.text = item; select.appendChild(option); } this._select = select; } private _bindEvents() { this._select.addEventListener('keydown', this._onKeyDown.bind(this)); this._select.addEventListener('blur', this._onBlur.bind(this)); } private _onKeyDown(event: KeyboardEvent) { switch (getKeyboardLayout().keyForKeydownEvent(event)) { case 'Enter': this.commit(event.shiftKey ? 'up' : 'down'); break; case 'Tab': this.commit(event.shiftKey ? 'left' : 'right'); event.stopPropagation(); event.preventDefault(); break; case 'Escape': this.cancel(); break; default: break; } } private _onBlur(event: FocusEvent) { if (this.isDisposed) { return; } if (!this.commit()) { event.preventDefault(); event.stopPropagation(); this._select.focus(); } } private _select: HTMLSelectElement; private _isMultiSelect: boolean = false; } /** * Cell editor for option cells whose value can be any value * from set of pre-defined options or values that can be input by user. */ export class DynamicOptionCellEditor extends CellEditor { /** * Handle the DOM events for the editor. * * @param event - The DOM event sent to the editor. */ handleEvent(event: Event): void { switch (event.type) { case 'keydown': this._onKeyDown(event as KeyboardEvent); break; case 'blur': this._onBlur(event as FocusEvent); break; } } /** * Dispose of the resources held by cell editor. */ dispose() { if (this.isDisposed) { return; } this._unbindEvents(); super.dispose(); } /** * Start editing the cell. */ protected startEditing() { this._createWidget(); const cell = this.cell; const cellInfo = this.getCellInfo(cell); this._input.value = this._deserialize(cellInfo.data); this.editorContainer.appendChild(this._input); this._input.focus(); this._input.select(); this._bindEvents(); } /** * Return the current option input. */ protected getInput(): string | null { return this._input.value; } private _deserialize(value: any): any { if (value === null || value === undefined) { return ''; } return value.toString(); } private _createWidget() { const cell = this.cell; const grid = cell.grid; const dataModel = grid.dataModel!; const rowCount = dataModel.rowCount('body'); const listId = 'cell-editor-list'; const list = document.createElement('datalist'); list.id = listId; const input = document.createElement('input'); input.classList.add('lm-DataGrid-cellEditorWidget'); input.classList.add('lm-DataGrid-cellEditorInput'); const valueSet = new Set(); for (let r = 0; r < rowCount; ++r) { const data = dataModel.data('body', r, cell.column); if (data) { valueSet.add(data); } } valueSet.forEach((value: string) => { const option = document.createElement('option'); option.value = value; option.text = value; list.appendChild(option); }); this.editorContainer.appendChild(list); input.setAttribute('list', listId); this._input = input; } private _bindEvents() { this._input.addEventListener('keydown', this); this._input.addEventListener('blur', this); } private _unbindEvents() { this._input.removeEventListener('keydown', this); this._input.removeEventListener('blur', this); } private _onKeyDown(event: KeyboardEvent) { switch (getKeyboardLayout().keyForKeydownEvent(event)) { case 'Enter': this.commit(event.shiftKey ? 'up' : 'down'); break; case 'Tab': this.commit(event.shiftKey ? 'left' : 'right'); event.stopPropagation(); event.preventDefault(); break; case 'Escape': this.cancel(); break; default: break; } } private _onBlur(event: FocusEvent) { if (this.isDisposed) { return; } if (!this.commit()) { event.preventDefault(); event.stopPropagation(); this._input.focus(); } } private _input: HTMLInputElement; } /** * The namespace for the `CellEditor` class statics. */ export namespace CellEditor { /** * An object which holds the configuration data for a cell. */ export type CellConfig = { /** * The grid containing the cell. */ readonly grid: DataGrid; /** * The row index of the cell. */ readonly row: number; /** * The column index of the cell. */ readonly column: number; }; } /** * A namespace for module-private functionality. */ namespace Private { /** * A type alias for cell properties. */ export type ICellInfo = { grid: DataGrid; row: number; column: number; data: any; x: number; y: number; width: number; height: number; }; } lumino-2021.12.13/packages/datagrid/src/celleditorcontroller.ts000066400000000000000000000247451415564225700243630ustar00rootroot00000000000000/*----------------------------------------------------------------------------- | Copyright (c) 2014-2019, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { BooleanCellEditor, CellDataType, CellEditor, DateCellEditor, DynamicOptionCellEditor, ICellEditOptions, ICellEditor, ICellEditResponse, IntegerCellEditor, NumberCellEditor, OptionCellEditor, TextCellEditor } from './celleditor'; import { CellGroup } from './cellgroup'; import { DataModel, MutableDataModel } from './datamodel'; /** * A type alias for cell editor override identifier. */ export type EditorOverrideIdentifier = | CellDataType | DataModel.Metadata | 'default'; /** * An object which manages cell editing. */ export interface ICellEditorController { /** * Override cell editor for the cells matching the identifier. * * @param identifier - Cell identifier to use when matching cells. * if identifier is a CellDataType, then cell matching is done using data type of the cell, * if identifier is a Metadata, then partial match of the cell metadata with identifier is used for match, * if identifier is 'default' then override is used as default editor when no other editor is found suitable * * @param editor - The cell editor to use or resolver to use to get an editor for matching cells. */ setEditor( identifier: EditorOverrideIdentifier, editor: ICellEditor | Resolver ): void; /** * Start editing a cell. * * @param cell - The object holding cell configuration data. * * @param options - The cell editing options. */ edit(cell: CellEditor.CellConfig, options?: ICellEditOptions): boolean; /** * Cancel editing. */ cancel(): void; } /** * A type alias for a cell editor config function. * * This type is used to compute a value from a cell config object. */ export type ConfigFunc = (config: CellEditor.CellConfig) => T; /** * A type alias for a cell editor config option. * * A config option can be a static value or a config function. */ export type ConfigOption = T | ConfigFunc; /** * A type alias for a cell editor resolver function. */ export type Resolver = ConfigFunc; /** * Resolve a config option for a cell editor. * * @param option - The config option to resolve. * * @param config - The cell config object. * * @returns The resolved value for the option. */ export function resolveOption( option: ConfigOption, config: CellEditor.CellConfig ): T { return typeof option === 'function' ? (option as ConfigFunc)(config) : option; } /** * An object which manages cell editing. It stores editor overrides, * decides which editor to use for a cell, makes sure there is only one editor active. */ export class CellEditorController implements ICellEditorController { /** * Override cell editor for the cells matching the identifier. * * @param identifier - Cell identifier to use when matching cells. * if identifier is a CellDataType, then cell matching is done using data type of the cell, * if identifier is a Metadata, then partial match of the cell metadata with identifier is used for match, * if identifier is 'default' then override is used as default editor when no other editor is found suitable * * @param editor - The cell editor to use or resolver to use to get an editor for matching cells. */ setEditor( identifier: EditorOverrideIdentifier, editor: ICellEditor | Resolver ) { if (typeof identifier === 'string') { this._typeBasedOverrides.set(identifier, editor); } else { const key = this._metadataIdentifierToKey(identifier); this._metadataBasedOverrides.set(key, [identifier, editor]); } } /** * Start editing a cell. * * @param cell - The object holding cell configuration data. * * @param options - The cell editing options. */ edit(cell: CellEditor.CellConfig, options?: ICellEditOptions): boolean { const grid = cell.grid; if (!grid.editable) { console.error('Grid cannot be edited!'); return false; } this.cancel(); this._cell = cell; options = options || {}; options.onCommit = options.onCommit || this._onCommit.bind(this); options.onCancel = options.onCancel || this._onCancel.bind(this); // if an editor is passed in with options, then use it for editing if (options.editor) { this._editor = options.editor; options.editor.edit(cell, options); return true; } // choose an editor based on overrides / cell data type const editor = this._getEditor(cell); if (editor) { this._editor = editor; editor.edit(cell, options); return true; } return false; } /** * Cancel editing. */ cancel(): void { if (this._editor) { this._editor.cancel(); this._editor = null; } this._cell = null; } private _onCommit(response: ICellEditResponse): void { const cell = this._cell; if (!cell) { return; } const grid = cell.grid; const dataModel = grid.dataModel as MutableDataModel; let row = cell.row; let column = cell.column; const cellGroup = CellGroup.getGroup(grid.dataModel!, 'body', row, column); if (cellGroup) { row = cellGroup.r1; column = cellGroup.c1; } dataModel.setData('body', row, column, response.value); grid.viewport.node.focus(); if (response.cursorMovement !== 'none') { grid.moveCursor(response.cursorMovement); grid.scrollToCursor(); } } private _onCancel(): void { if (!this._cell) { return; } this._cell.grid.viewport.node.focus(); } private _getDataTypeKey(cell: CellEditor.CellConfig): string { const metadata = cell.grid.dataModel ? cell.grid.dataModel.metadata('body', cell.row, cell.column) : null; if (!metadata) { return 'default'; } let key = ''; if (metadata) { key = metadata.type; } if (metadata.constraint && metadata.constraint.enum) { if (metadata.constraint.enum === 'dynamic') { key += ':dynamic-option'; } else { key += ':option'; } } return key; } private _objectToKey(object: any): string { let str = ''; for (let key in object) { const value = object[key]; if (typeof value === 'object') { str += `${key}:${this._objectToKey(value)}`; } else { str += `[${key}:${value}]`; } } return str; } private _metadataIdentifierToKey(metadata: DataModel.Metadata): string { return this._objectToKey(metadata); } private _metadataMatchesIdentifier( metadata: DataModel.Metadata, identifier: DataModel.Metadata ): boolean { for (let key in identifier) { if (!metadata.hasOwnProperty(key)) { return false; } const identifierValue = identifier[key]; const metadataValue = metadata[key]; if (typeof identifierValue === 'object') { if (!this._metadataMatchesIdentifier(metadataValue, identifierValue)) { return false; } } else if (metadataValue !== identifierValue) { return false; } } return true; } private _getMetadataBasedEditor( cell: CellEditor.CellConfig ): ICellEditor | undefined { let editorMatched: ICellEditor | undefined; const metadata = cell.grid.dataModel!.metadata( 'body', cell.row, cell.column ); if (metadata) { this._metadataBasedOverrides.forEach(value => { if (!editorMatched) { let [identifier, editor] = value; if (this._metadataMatchesIdentifier(metadata, identifier)) { editorMatched = resolveOption(editor, cell); } } }); } return editorMatched; } /** * Choose the most appropriate cell editor to use based on overrides / cell data type. * * If no match is found in overrides or based on cell data type, and if cell has a primitive * data type then TextCellEditor is used as default cell editor. If 'default' cell editor * is overridden, then it is used instead of TextCellEditor for default. */ private _getEditor(cell: CellEditor.CellConfig): ICellEditor | undefined { const dtKey = this._getDataTypeKey(cell); // find an editor based on data type based override if (this._typeBasedOverrides.has(dtKey)) { const editor = this._typeBasedOverrides.get(dtKey); return resolveOption(editor!, cell); } // find an editor based on metadata match based override else if (this._metadataBasedOverrides.size > 0) { const editor = this._getMetadataBasedEditor(cell); if (editor) { return editor; } } // choose an editor based on data type switch (dtKey) { case 'string': return new TextCellEditor(); case 'number': return new NumberCellEditor(); case 'integer': return new IntegerCellEditor(); case 'boolean': return new BooleanCellEditor(); case 'date': return new DateCellEditor(); case 'string:option': case 'number:option': case 'integer:option': case 'date:option': case 'array:option': return new OptionCellEditor(); case 'string:dynamic-option': case 'number:dynamic-option': case 'integer:dynamic-option': case 'date:dynamic-option': return new DynamicOptionCellEditor(); } // if an override exists for 'default', then use it if (this._typeBasedOverrides.has('default')) { const editor = this._typeBasedOverrides.get('default'); return resolveOption(editor!, cell); } // if cell has a primitive data type then use TextCellEditor const data = cell.grid.dataModel!.data('body', cell.row, cell.column); if (!data || typeof data !== 'object') { return new TextCellEditor(); } // no suitable editor found for the cell return undefined; } // active cell editor private _editor: ICellEditor | null = null; // active cell being edited private _cell: CellEditor.CellConfig | null = null; // cell editor overrides based on cell data type identifier private _typeBasedOverrides: Map = new Map(); // cell editor overrides based on partial metadata match private _metadataBasedOverrides: Map< string, [DataModel.Metadata, ICellEditor | Resolver] > = new Map(); } lumino-2021.12.13/packages/datagrid/src/cellgroup.ts000066400000000000000000000263131415564225700221160ustar00rootroot00000000000000import { DataModel } from './datamodel'; import { SectionList } from './sectionlist'; /** * An interface describing a merged cell group. * r1: start row * r2: end row * c1: start column * c2: end column */ export interface CellGroup { r1: number; r2: number; c1: number; c2: number; } /** * A collection of helper functions relating to merged cell groups */ export namespace CellGroup { export function areCellsMerged( dataModel: DataModel, rgn: DataModel.CellRegion, cell1: number[], cell2: number[] ): boolean { const numGroups = dataModel.groupCount(rgn); const [row1, column1] = cell1; const [row2, column2] = cell2; for (let i = 0; i < numGroups; i++) { const group = dataModel.group(rgn, i)!; if ( row1 >= group.r1 && row1 <= group.r2 && column1 >= group.c1 && column1 <= group.c2 && row2 >= group.r1 && row2 <= group.r2 && column2 >= group.c1 && column2 <= group.c2 ) { return true; } } return false; } /** * Calculates the cell boundary offsets needed for * a row or column at the given index by taking * into account merged cell groups in the region. * @param dataModel * @param regions * @param axis * @param sectionList * @param index */ export function calculateMergeOffsets( dataModel: DataModel, regions: DataModel.CellRegion[], axis: 'row' | 'column', sectionList: SectionList, index: number ): [number, number, CellGroup] { let mergeStartOffset = 0; let mergeEndOffset = 0; let mergedCellGroups: CellGroup[] = []; for (const region of regions) { mergedCellGroups = mergedCellGroups.concat( getCellGroupsAtRegion(dataModel, region) ); } let groupsAtAxis: CellGroup[] = []; if (axis === 'row') { for (const region of regions) { groupsAtAxis = groupsAtAxis.concat( getCellGroupsAtRow(dataModel, region, index) ); } } else { for (const region of regions) { groupsAtAxis = groupsAtAxis.concat( getCellGroupsAtColumn(dataModel, region, index) ); } } if (groupsAtAxis.length === 0) { return [0, 0, { r1: -1, r2: -1, c1: -1, c2: -1 }]; } let joinedGroup = groupsAtAxis[0]; for (let g = 0; g < mergedCellGroups.length; g++) { const group = mergedCellGroups[g]; if (areCellGroupsIntersectingAtAxis(joinedGroup, group, axis)) { joinedGroup = joinCellGroups([group, joinedGroup]); mergedCellGroups.splice(g, 1); g = 0; } } let minRow = joinedGroup.r1; let maxRow = joinedGroup.r2; for (let r = index - 1; r >= minRow; r--) { mergeStartOffset += sectionList.sizeOf(r); } for (let r = index + 1; r <= maxRow; r++) { mergeEndOffset += sectionList.sizeOf(r); } return [mergeStartOffset, mergeEndOffset, joinedGroup]; } /** * Checks if two cell-groups are intersecting * in the given axis. * @param group1 * @param group2 * @param axis */ export function areCellGroupsIntersectingAtAxis( group1: CellGroup, group2: CellGroup, axis: 'row' | 'column' ): boolean { if (axis === 'row') { return ( (group1.r1 >= group2.r1 && group1.r1 <= group2.r2) || (group1.r2 >= group2.r1 && group1.r2 <= group2.r2) || (group2.r1 >= group1.r1 && group2.r1 <= group1.r2) || (group2.r2 >= group1.r1 && group2.r2 <= group1.r2) ); } return ( (group1.c1 >= group2.c1 && group1.c1 <= group2.c2) || (group1.c2 >= group2.c1 && group1.c2 <= group2.c2) || (group2.c1 >= group1.c1 && group2.c1 <= group1.c2) || (group2.c2 >= group1.c1 && group2.c2 <= group1.c2) ); } /** * Checks if cell-groups are intersecting. * @param group1 * @param group2 */ export function areCellGroupsIntersecting( group1: CellGroup, group2: CellGroup ): boolean { return ( ((group1.r1 >= group2.r1 && group1.r1 <= group2.r2) || (group1.r2 >= group2.r1 && group1.r2 <= group2.r2) || (group2.r1 >= group1.r1 && group2.r1 <= group1.r2) || (group2.r2 >= group1.r1 && group2.r2 <= group1.r2)) && ((group1.c1 >= group2.c1 && group1.c1 <= group2.c2) || (group1.c2 >= group2.c1 && group1.c2 <= group2.c2) || (group2.c1 >= group1.c1 && group2.c1 <= group1.c2) || (group2.c2 >= group1.c1 && group2.c2 <= group1.c2)) ); } /** * Retrieves the index of the cell-group to which * the cell at the given row, column belongs. * @param dataModel * @param rgn * @param row * @param column */ export function getGroupIndex( dataModel: DataModel, rgn: DataModel.CellRegion, row: number, column: number ): number { const numGroups = dataModel.groupCount(rgn); for (let i = 0; i < numGroups; i++) { const group = dataModel.group(rgn, i)!; if ( row >= group.r1 && row <= group.r2 && column >= group.c1 && column <= group.c2 ) { return i; } } return -1; } /** * Returns a cell-group for the given row/index coordinates. * @param dataModel * @param rgn * @param row * @param column */ export function getGroup( dataModel: DataModel, rgn: DataModel.CellRegion, row: number, column: number ): CellGroup | null { const groupIndex = getGroupIndex(dataModel, rgn, row, column); if (groupIndex === -1) { return null; } return dataModel.group(rgn, groupIndex); } /** * Returns all cell groups which belong to * a given cell cell region. * @param dataModel * @param rgn */ export function getCellGroupsAtRegion( dataModel: DataModel, rgn: DataModel.CellRegion ): CellGroup[] { let groupsAtRegion: CellGroup[] = []; const numGroups = dataModel.groupCount(rgn); for (let i = 0; i < numGroups; i++) { const group = dataModel.group(rgn, i)!; groupsAtRegion.push(group); } return groupsAtRegion; } /** * Calculates and returns a merged cell-group from * two cell-group objects. * @param groups */ export function joinCellGroups(groups: CellGroup[]): CellGroup { let startRow = Number.MAX_VALUE; let endRow = Number.MIN_VALUE; let startColumn = Number.MAX_VALUE; let endColumn = Number.MIN_VALUE; for (const group of groups) { startRow = Math.min(startRow, group.r1); endRow = Math.max(endRow, group.r2); startColumn = Math.min(startColumn, group.c1); endColumn = Math.max(endColumn, group.c2); } return { r1: startRow, r2: endRow, c1: startColumn, c2: endColumn }; } /** * Merges a cell group with other cells groups in the * same region if they intersect. * @param dataModel the data model of the grid. * @param group the target cell group. * @param region the region of the cell group. * @returns a new cell group after merging has happened. */ export function joinCellGroupWithMergedCellGroups( dataModel: DataModel, group: CellGroup, region: DataModel.CellRegion ): CellGroup { let joinedGroup: CellGroup = { ...group }; const mergedCellGroups: CellGroup[] = getCellGroupsAtRegion( dataModel, region ); for (let g = 0; g < mergedCellGroups.length; g++) { const mergedGroup = mergedCellGroups[g]; if (areCellGroupsIntersecting(joinedGroup, mergedGroup)) { joinedGroup = joinCellGroups([joinedGroup, mergedGroup]); } } return joinedGroup; } /** * Retrieves a list of cell groups intersecting at * a given row. * @param dataModel data model of the grid. * @param rgn the cell region. * @param row the target row to look for intersections at. * @returns all cell groups intersecting with the row. */ export function getCellGroupsAtRow( dataModel: DataModel, rgn: DataModel.CellRegion, row: number ): CellGroup[] { let groupsAtRow = []; const numGroups = dataModel.groupCount(rgn); for (let i = 0; i < numGroups; i++) { const group = dataModel.group(rgn, i)!; if (row >= group.r1 && row <= group.r2) { groupsAtRow.push(group); } } return groupsAtRow; } /** * Retrieves a list of cell groups intersecting at * a given column. * @param dataModel data model of the grid. * @param rgn the cell region. * @param column the target column to look for intersections at. * @returns all cell groups intersecting with the column. */ export function getCellGroupsAtColumn( dataModel: DataModel, rgn: DataModel.CellRegion, column: number ): CellGroup[] { let groupsAtColumn = []; const numGroups = dataModel.groupCount(rgn); for (let i = 0; i < numGroups; i++) { const group = dataModel.group(rgn, i)!; if (column >= group.c1 && column <= group.c2) { groupsAtColumn.push(group); } } return groupsAtColumn; } /** * Checks if cell group 1 is above cell group 2. * @param group1 cell group 1. * @param group2 cell group 2. * @returns boolean. */ export function isCellGroupAbove( group1: CellGroup, group2: CellGroup ): boolean { return group2.r2 >= group1.r1; } /** * Checks if cell group 1 is below cell group 2. */ export function isCellGroupBelow( group1: CellGroup, group2: CellGroup ): boolean { return group2.r1 <= group1.r2; } /** * Merges a target cell group with any cell groups * it intersects with at a given row or column. * @param dataModel data model of the grid. * @param regions list of cell regions. * @param axis row or column. * @param group the target cell group. * @returns a new merged cell group. */ export function joinCellGroupsIntersectingAtAxis( dataModel: DataModel, regions: DataModel.CellRegion[], axis: 'row' | 'column', group: CellGroup ): CellGroup { let groupsAtAxis: CellGroup[] = []; if (axis === 'row') { for (const region of regions) { for (let r = group.r1; r <= group.r2; r++) { groupsAtAxis = groupsAtAxis.concat( CellGroup.getCellGroupsAtRow(dataModel, region, r) ); } } } else { for (const region of regions) { for (let c = group.c1; c <= group.c2; c++) { groupsAtAxis = groupsAtAxis.concat( CellGroup.getCellGroupsAtColumn(dataModel, region, c) ); } } } let mergedGroupAtAxis: CellGroup = CellGroup.joinCellGroups(groupsAtAxis); if (groupsAtAxis.length > 0) { let mergedCellGroups: CellGroup[] = []; for (const region of regions) { mergedCellGroups = mergedCellGroups.concat( CellGroup.getCellGroupsAtRegion(dataModel, region) ); } for (let g = 0; g < mergedCellGroups.length; g++) { const group = mergedCellGroups[g]; if ( CellGroup.areCellGroupsIntersectingAtAxis( mergedGroupAtAxis, group, axis ) ) { mergedGroupAtAxis = CellGroup.joinCellGroups([ group, mergedGroupAtAxis ]); mergedCellGroups.splice(g, 1); g = 0; } } } return mergedGroupAtAxis; } } lumino-2021.12.13/packages/datagrid/src/cellrenderer.ts000066400000000000000000000066211415564225700225700ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2019, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { DataModel } from './datamodel'; import { GraphicsContext } from './graphicscontext'; /** * An object which renders the cells of a data grid. * * #### Notes * If the predefined cell renderers are insufficient for a particular * use case, a custom cell renderer can be defined which derives from * this class. * * The data grid renders cells in column-major order, by region. The * region order is: body, row header, column header, corner header. */ export abstract class CellRenderer { /** * Paint the content for a cell. * * @param gc - The graphics context to use for drawing. * * @param config - The configuration data for the cell. * * #### Notes * The grid will save/restore the `gc` state before/after invoking * the renderer. * * For performance, the cell content is efficiently clipped to the * width of the column, but *the height is not clipped*. If height * clipping is needed, the renderer must set up its own clip rect. * * The renderer **must not** draw outside the cell bounding height. */ abstract paint(gc: GraphicsContext, config: CellRenderer.CellConfig): void; } /** * The namespace for the `CellRenderer` class statics. */ export namespace CellRenderer { /** * An object which holds the configuration data for a cell. */ export type CellConfig = { /** * The X position of the cell rectangle, in viewport coordinates. */ readonly x: number; /** * The Y position of the cell rectangle, in viewport coordinates. */ readonly y: number; /** * The height of the cell rectangle, in viewport pixels. */ readonly height: number; /** * The width of the cell rectangle, in viewport pixels. */ readonly width: number; /** * The region for the cell. */ readonly region: DataModel.CellRegion; /** * The row index of the cell. */ readonly row: number; /** * The column index of the cell. */ readonly column: number; /** * The value for the cell. */ readonly value: any; /** * The metadata for the cell. */ readonly metadata: DataModel.Metadata; }; /** * A type alias for a cell renderer config function. * * This type is used to compute a value from a cell config object. */ export type ConfigFunc = (config: CellConfig) => T; /** * A type alias for a cell renderer config option. * * A config option can be a static value or a config function. */ export type ConfigOption = T | ConfigFunc; /** * Resolve a config option for a cell renderer. * * @param option - The config option to resolve. * * @param config - The cell config object. * * @returns The resolved value for the option. */ export function resolveOption( option: ConfigOption, config: CellConfig ): T { return typeof option === 'function' ? (option as ConfigFunc)(config) : option; } } lumino-2021.12.13/packages/datagrid/src/datagrid.ts000066400000000000000000005402561415564225700217100ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2019, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { toArray } from '@lumino/algorithm'; import { IDisposable } from '@lumino/disposable'; import { ClipboardExt, ElementExt, Platform } from '@lumino/domutils'; import { ConflatableMessage, IMessageHandler, Message, MessageLoop } from '@lumino/messaging'; import { GridLayout, ScrollBar, Widget } from '@lumino/widgets'; import { CellRenderer } from './cellrenderer'; import { DataModel, MutableDataModel } from './datamodel'; import { CellGroup } from './cellgroup'; import { GraphicsContext } from './graphicscontext'; import { RendererMap } from './renderermap'; import { SectionList } from './sectionlist'; import { SelectionModel } from './selectionmodel'; import { CellEditorController, ICellEditorController } from './celleditorcontroller'; import { JSONExt } from '@lumino/coreutils'; import { TextRenderer } from './textrenderer'; /** * A widget which implements a high-performance tabular data grid. * * #### Notes * A data grid is implemented as a composition of child widgets. These * child widgets are considered an implementation detail. Manipulating * the child widgets of a data grid directly is undefined behavior. * * This class is not designed to be subclassed. */ export class DataGrid extends Widget { /** * Construct a new data grid. * * @param options - The options for initializing the data grid. */ constructor(options: DataGrid.IOptions = {}) { super(); this.addClass('lm-DataGrid'); /* */ this.addClass('p-DataGrid'); /* */ // Parse the simple options. this._style = options.style || DataGrid.defaultStyle; this._stretchLastRow = options.stretchLastRow || false; this._stretchLastColumn = options.stretchLastColumn || false; this._headerVisibility = options.headerVisibility || 'all'; this._cellRenderers = options.cellRenderers || new RendererMap(); this._copyConfig = options.copyConfig || DataGrid.defaultCopyConfig; // Connect to the renderer map changed signal. this._cellRenderers.changed.connect(this._onRenderersChanged, this); // Parse the default sizes. let defaultSizes = options.defaultSizes || DataGrid.defaultSizes; let minimumSizes = options.minimumSizes || DataGrid.minimumSizes; // Set up the sections lists. this._rowSections = new SectionList({ defaultSize: defaultSizes.rowHeight, minimumSize: minimumSizes.rowHeight }); this._columnSections = new SectionList({ defaultSize: defaultSizes.columnWidth, minimumSize: minimumSizes.columnWidth }); this._rowHeaderSections = new SectionList({ defaultSize: defaultSizes.rowHeaderWidth, minimumSize: minimumSizes.rowHeaderWidth }); this._columnHeaderSections = new SectionList({ defaultSize: defaultSizes.columnHeaderHeight, minimumSize: minimumSizes.columnHeaderHeight }); // Create the canvas, buffer, and overlay objects. this._canvas = Private.createCanvas(); this._buffer = Private.createCanvas(); this._overlay = Private.createCanvas(); // Get the graphics contexts for the canvases. this._canvasGC = this._canvas.getContext('2d')!; this._bufferGC = this._buffer.getContext('2d')!; this._overlayGC = this._overlay.getContext('2d')!; // Set up the on-screen canvas. this._canvas.style.position = 'absolute'; this._canvas.style.top = '0px'; this._canvas.style.left = '0px'; this._canvas.style.width = '0px'; this._canvas.style.height = '0px'; // Set up the on-screen overlay. this._overlay.style.position = 'absolute'; this._overlay.style.top = '0px'; this._overlay.style.left = '0px'; this._overlay.style.width = '0px'; this._overlay.style.height = '0px'; // Create the internal widgets for the data grid. this._viewport = new Widget(); this._viewport.node.tabIndex = -1; this._viewport.node.style.outline = 'none'; this._vScrollBar = new ScrollBar({ orientation: 'vertical' }); this._hScrollBar = new ScrollBar({ orientation: 'horizontal' }); this._scrollCorner = new Widget(); this._editorController = new CellEditorController(); // Add the extra class names to the child widgets. this._viewport.addClass('lm-DataGrid-viewport'); this._vScrollBar.addClass('lm-DataGrid-scrollBar'); this._hScrollBar.addClass('lm-DataGrid-scrollBar'); this._scrollCorner.addClass('lm-DataGrid-scrollCorner'); /* */ this._viewport.addClass('p-DataGrid-viewport'); this._vScrollBar.addClass('p-DataGrid-scrollBar'); this._hScrollBar.addClass('p-DataGrid-scrollBar'); this._scrollCorner.addClass('p-DataGrid-scrollCorner'); /* */ // Add the on-screen canvas to the viewport node. this._viewport.node.appendChild(this._canvas); // Add the on-screen overlay to the viewport node. this._viewport.node.appendChild(this._overlay); // Install the message hooks. MessageLoop.installMessageHook(this._viewport, this); MessageLoop.installMessageHook(this._hScrollBar, this); MessageLoop.installMessageHook(this._vScrollBar, this); // Hide the scroll bars and corner from the outset. this._vScrollBar.hide(); this._hScrollBar.hide(); this._scrollCorner.hide(); // Connect to the scroll bar signals. this._vScrollBar.thumbMoved.connect(this._onThumbMoved, this); this._hScrollBar.thumbMoved.connect(this._onThumbMoved, this); this._vScrollBar.pageRequested.connect(this._onPageRequested, this); this._hScrollBar.pageRequested.connect(this._onPageRequested, this); this._vScrollBar.stepRequested.connect(this._onStepRequested, this); this._hScrollBar.stepRequested.connect(this._onStepRequested, this); // Set the layout cell config for the child widgets. GridLayout.setCellConfig(this._viewport, { row: 0, column: 0 }); GridLayout.setCellConfig(this._vScrollBar, { row: 0, column: 1 }); GridLayout.setCellConfig(this._hScrollBar, { row: 1, column: 0 }); GridLayout.setCellConfig(this._scrollCorner, { row: 1, column: 1 }); // Create the layout for the data grid. let layout = new GridLayout({ rowCount: 2, columnCount: 2, rowSpacing: 0, columnSpacing: 0, fitPolicy: 'set-no-constraint' }); // Set the stretch factors for the grid. layout.setRowStretch(0, 1); layout.setRowStretch(1, 0); layout.setColumnStretch(0, 1); layout.setColumnStretch(1, 0); // Add the child widgets to the layout. layout.addWidget(this._viewport); layout.addWidget(this._vScrollBar); layout.addWidget(this._hScrollBar); layout.addWidget(this._scrollCorner); // Install the layout on the data grid. this.layout = layout; } /** * Dispose of the resources held by the widgets. */ dispose(): void { // Release the mouse. this._releaseMouse(); // Dispose of the handlers. if (this._keyHandler) { this._keyHandler.dispose(); } if (this._mouseHandler) { this._mouseHandler.dispose(); } this._keyHandler = null; this._mouseHandler = null; // Clear the models. this._dataModel = null; this._selectionModel = null; // Clear the section lists. this._rowSections.clear(); this._columnSections.clear(); this._rowHeaderSections.clear(); this._columnHeaderSections.clear(); // Dispose of the base class. super.dispose(); } /** * Get the data model for the data grid. */ get dataModel(): DataModel | null { return this._dataModel; } /** * Set the data model for the data grid. * * #### Notes * This will automatically remove the current selection model. */ set dataModel(value: DataModel | null) { // Do nothing if the model does not change. if (this._dataModel === value) { return; } // Release the mouse. this._releaseMouse(); // Clear the selection model. this.selectionModel = null; // Disconnect the change handler from the old model. if (this._dataModel) { this._dataModel.changed.disconnect(this._onDataModelChanged, this); } // Connect the change handler for the new model. if (value) { value.changed.connect(this._onDataModelChanged, this); } // Update the internal model reference. this._dataModel = value; // Clear the section lists. this._rowSections.clear(); this._columnSections.clear(); this._rowHeaderSections.clear(); this._columnHeaderSections.clear(); // Populate the section lists. if (value) { this._rowSections.insert(0, value.rowCount('body')); this._columnSections.insert(0, value.columnCount('body')); this._rowHeaderSections.insert(0, value.columnCount('row-header')); this._columnHeaderSections.insert(0, value.rowCount('column-header')); } // Reset the scroll position. this._scrollX = 0; this._scrollY = 0; // Sync the viewport. this._syncViewport(); } /** * Get the selection model for the data grid. */ get selectionModel(): SelectionModel | null { return this._selectionModel; } /** * Set the selection model for the data grid. */ set selectionModel(value: SelectionModel | null) { // Do nothing if the selection model does not change. if (this._selectionModel === value) { return; } // Release the mouse. this._releaseMouse(); // Ensure the data models are a match. if (value && value.dataModel !== this._dataModel) { throw new Error('SelectionModel.dataModel !== DataGrid.dataModel'); } // Disconnect the change handler from the old model. if (this._selectionModel) { this._selectionModel.changed.disconnect(this._onSelectionsChanged, this); } // Connect the change handler for the new model. if (value) { value.changed.connect(this._onSelectionsChanged, this); } // Update the internal selection model reference. this._selectionModel = value; // Schedule a repaint of the overlay. this.repaintOverlay(); } /** * Get the key handler for the data grid. */ get keyHandler(): DataGrid.IKeyHandler | null { return this._keyHandler; } /** * Set the key handler for the data grid. */ set keyHandler(value: DataGrid.IKeyHandler | null) { this._keyHandler = value; } /** * Get the mouse handler for the data grid. */ get mouseHandler(): DataGrid.IMouseHandler | null { return this._mouseHandler; } /** * Set the mouse handler for the data grid. */ set mouseHandler(value: DataGrid.IMouseHandler | null) { // Bail early if the mouse handler does not change. if (this._mouseHandler === value) { return; } // Release the mouse. this._releaseMouse(); // Update the internal mouse handler. this._mouseHandler = value; } /** * Get the style for the data grid. */ get style(): DataGrid.Style { return this._style; } /** * Set the style for the data grid. */ set style(value: DataGrid.Style) { // Bail if the style does not change. if (this._style === value) { return; } // Update the internal style. this._style = { ...value }; // Schedule a repaint of the content. this.repaintContent(); // Schedule a repaint of the overlay. this.repaintOverlay(); } /** * Get the cell renderer map for the data grid. */ get cellRenderers(): RendererMap { return this._cellRenderers; } /** * Set the cell renderer map for the data grid. */ set cellRenderers(value: RendererMap) { // Bail if the renderer map does not change. if (this._cellRenderers === value) { return; } // Disconnect the old map. this._cellRenderers.changed.disconnect(this._onRenderersChanged, this); // Connect the new map. value.changed.connect(this._onRenderersChanged, this); // Update the internal renderer map. this._cellRenderers = value; // Schedule a repaint of the grid content. this.repaintContent(); } /** * Get the header visibility for the data grid. */ get headerVisibility(): DataGrid.HeaderVisibility { return this._headerVisibility; } /** * Set the header visibility for the data grid. */ set headerVisibility(value: DataGrid.HeaderVisibility) { // Bail if the visibility does not change. if (this._headerVisibility === value) { return; } // Update the internal visibility. this._headerVisibility = value; // Sync the viewport. this._syncViewport(); } /** * Get the default sizes for the various sections of the data grid. */ get defaultSizes(): DataGrid.DefaultSizes { let rowHeight = this._rowSections.defaultSize; let columnWidth = this._columnSections.defaultSize; let rowHeaderWidth = this._rowHeaderSections.defaultSize; let columnHeaderHeight = this._columnHeaderSections.defaultSize; return { rowHeight, columnWidth, rowHeaderWidth, columnHeaderHeight }; } /** * Set the default sizes for the various sections of the data grid. */ set defaultSizes(value: DataGrid.DefaultSizes) { // Update the section default sizes. this._rowSections.defaultSize = value.rowHeight; this._columnSections.defaultSize = value.columnWidth; this._rowHeaderSections.defaultSize = value.rowHeaderWidth; this._columnHeaderSections.defaultSize = value.columnHeaderHeight; // Sync the viewport. this._syncViewport(); } /** * Get the minimum sizes for the various sections of the data grid. */ get minimumSizes(): DataGrid.DefaultSizes { let rowHeight = this._rowSections.minimumSize; let columnWidth = this._columnSections.minimumSize; let rowHeaderWidth = this._rowHeaderSections.minimumSize; let columnHeaderHeight = this._columnHeaderSections.minimumSize; return { rowHeight, columnWidth, rowHeaderWidth, columnHeaderHeight }; } /** * Set the minimum sizes for the various sections of the data grid. */ set minimumSizes(value: DataGrid.DefaultSizes) { // Update the section default sizes. this._rowSections.minimumSize = value.rowHeight; this._columnSections.minimumSize = value.columnWidth; this._rowHeaderSections.minimumSize = value.rowHeaderWidth; this._columnHeaderSections.minimumSize = value.columnHeaderHeight; // Sync the viewport. this._syncViewport(); } /** * Get the copy configuration for the data grid. */ get copyConfig(): DataGrid.CopyConfig { return this._copyConfig; } /** * Set the copy configuration for the data grid. */ set copyConfig(value: DataGrid.CopyConfig) { this._copyConfig = value; } /** * Get whether the last row is stretched. */ get stretchLastRow(): boolean { return this._stretchLastRow; } /** * Set whether the last row is stretched. */ set stretchLastRow(value: boolean) { // Bail early if the value does not change. if (value === this._stretchLastRow) { return; } // Update the internal value. this._stretchLastRow = value; // Sync the viewport this._syncViewport(); } /** * Get whether the last column is stretched. */ get stretchLastColumn(): boolean { return this._stretchLastColumn; } /** * Set whether the last column is stretched. */ set stretchLastColumn(value: boolean) { // Bail early if the value does not change. if (value === this._stretchLastColumn) { return; } // Update the internal value. this._stretchLastColumn = value; // Sync the viewport this._syncViewport(); } /** * The virtual width of the row headers. */ get headerWidth(): number { if (this._headerVisibility === 'none') { return 0; } if (this._headerVisibility === 'column') { return 0; } return this._rowHeaderSections.length; } /** * The virtual height of the column headers. */ get headerHeight(): number { if (this._headerVisibility === 'none') { return 0; } if (this._headerVisibility === 'row') { return 0; } return this._columnHeaderSections.length; } /** * The virtual width of the grid body. * * #### Notes * This does *not* account for a stretched last column. */ get bodyWidth(): number { return this._columnSections.length; } /** * The virtual height of the grid body. * * #### Notes * This does *not* account for a stretched last row. */ get bodyHeight(): number { return this._rowSections.length; } /** * The virtual width of the entire grid. * * #### Notes * This does *not* account for a stretched last column. */ get totalWidth(): number { return this.headerWidth + this.bodyWidth; } /** * The virtual height of the entire grid. * * #### Notes * This does *not* account for a stretched last row. */ get totalHeight(): number { return this.headerHeight + this.bodyHeight; } /** * The actual width of the viewport. */ get viewportWidth(): number { return this._viewportWidth; } /** * The actual height of the viewport. */ get viewportHeight(): number { return this._viewportHeight; } /** * The width of the visible portion of the grid body. */ get pageWidth(): number { return Math.max(0, this.viewportWidth - this.headerWidth); } /** * The height of the visible portion of the grid body. */ get pageHeight(): number { return Math.max(0, this.viewportHeight - this.headerHeight); } /** * The current scroll X position of the viewport. */ get scrollX(): number { return this._hScrollBar.value; } /** * The current scroll Y position of the viewport. */ get scrollY(): number { return this._vScrollBar.value; } /** * The maximum scroll X position for the grid. */ get maxScrollX(): number { return Math.max(0, this.bodyWidth - this.pageWidth - 1); } /** * The maximum scroll Y position for the grid. */ get maxScrollY(): number { return Math.max(0, this.bodyHeight - this.pageHeight - 1); } /** * The viewport widget for the data grid. */ get viewport(): Widget { return this._viewport; } /** * The cell editor controller object for the data grid. */ get editorController(): ICellEditorController | null { return this._editorController; } set editorController(controller: ICellEditorController | null) { this._editorController = controller; } /** * Whether the cell editing is enabled for the data grid. */ get editingEnabled(): boolean { return this._editingEnabled; } set editingEnabled(enabled: boolean) { this._editingEnabled = enabled; } /** * Whether the grid cells are editable. * * `editingEnabled` flag must be on and grid must have required * selection model, editor controller and data model properties. */ get editable(): boolean { return ( this._editingEnabled && this._selectionModel !== null && this._editorController !== null && this.dataModel instanceof MutableDataModel ); } /** * The rendering context for painting the data grid. */ protected get canvasGC(): CanvasRenderingContext2D { return this._canvasGC; } /** * The row sections of the data grid. */ protected get rowSections(): SectionList { return this._rowSections; } /** * The column sections of the data grid. */ protected get columnSections(): SectionList { return this._columnSections; } /** * The row header sections of the data grid. */ protected get rowHeaderSections(): SectionList { return this._rowHeaderSections; } /** * The column header sections of the data grid. */ protected get columnHeaderSections(): SectionList { return this._columnHeaderSections; } /** * Scroll the grid to the specified row. * * @param row - The row index of the cell. * * #### Notes * This is a no-op if the row is already visible. */ scrollToRow(row: number): void { // Fetch the row count. let nr = this._rowSections.count; // Bail early if there is no content. if (nr === 0) { return; } // Floor the row index. row = Math.floor(row); // Clamp the row index. row = Math.max(0, Math.min(row, nr - 1)); // Get the virtual bounds of the row. let y1 = this._rowSections.offsetOf(row); let y2 = this._rowSections.extentOf(row); // Get the virtual bounds of the viewport. let vy1 = this._scrollY; let vy2 = this._scrollY + this.pageHeight - 1; // Set up the delta variables. let dy = 0; // Compute the delta Y scroll. if (y1 < vy1) { dy = y1 - vy1 - 10; } else if (y2 > vy2) { dy = y2 - vy2 + 10; } // Bail early if no scroll is needed. if (dy === 0) { return; } // Scroll by the computed delta. this.scrollBy(0, dy); } /** * Scroll the grid to the specified column. * * @param column - The column index of the cell. * * #### Notes * This is a no-op if the column is already visible. */ scrollToColumn(column: number): void { // Fetch the column count. let nc = this._columnSections.count; // Bail early if there is no content. if (nc === 0) { return; } // Floor the column index. column = Math.floor(column); // Clamp the column index. column = Math.max(0, Math.min(column, nc - 1)); // Get the virtual bounds of the column. let x1 = this._columnSections.offsetOf(column); let x2 = this._columnSections.extentOf(column); // Get the virtual bounds of the viewport. let vx1 = this._scrollX; let vx2 = this._scrollX + this.pageWidth - 1; // Set up the delta variables. let dx = 0; // Compute the delta X scroll. if (x1 < vx1) { dx = x1 - vx1 - 10; } else if (x2 > vx2) { dx = x2 - vx2 + 10; } // Bail early if no scroll is needed. if (dx === 0) { return; } // Scroll by the computed delta. this.scrollBy(dx, 0); } /** * Scroll the grid to the specified cell. * * @param row - The row index of the cell. * * @param column - The column index of the cell. * * #### Notes * This is a no-op if the cell is already visible. */ scrollToCell(row: number, column: number): void { // Fetch the row and column count. let nr = this._rowSections.count; let nc = this._columnSections.count; // Bail early if there is no content. if (nr === 0 || nc === 0) { return; } // Floor the cell index. row = Math.floor(row); column = Math.floor(column); // Clamp the cell index. row = Math.max(0, Math.min(row, nr - 1)); column = Math.max(0, Math.min(column, nc - 1)); // Get the virtual bounds of the cell. let x1 = this._columnSections.offsetOf(column); let x2 = this._columnSections.extentOf(column); let y1 = this._rowSections.offsetOf(row); let y2 = this._rowSections.extentOf(row); // Get the virtual bounds of the viewport. let vx1 = this._scrollX; let vx2 = this._scrollX + this.pageWidth - 1; let vy1 = this._scrollY; let vy2 = this._scrollY + this.pageHeight - 1; // Set up the delta variables. let dx = 0; let dy = 0; // Compute the delta X scroll. if (x1 < vx1) { dx = x1 - vx1 - 10; } else if (x2 > vx2) { dx = x2 - vx2 + 10; } // Compute the delta Y scroll. if (y1 < vy1) { dy = y1 - vy1 - 10; } else if (y2 > vy2) { dy = y2 - vy2 + 10; } // Bail early if no scroll is needed. if (dx === 0 && dy === 0) { return; } // Scroll by the computed delta. this.scrollBy(dx, dy); } /** * Move cursor down/up/left/right while making sure it remains * within the bounds of selected rectangles * * @param direction - The direction of the movement. */ moveCursor(direction: SelectionModel.CursorMoveDirection): void { // Bail early if there is no selection if ( !this.dataModel || !this._selectionModel || this._selectionModel.isEmpty ) { return; } const iter = this._selectionModel.selections(); const onlyOne = iter.next() && !iter.next(); // if there is a single selection that is a single cell selection // then move the selection and cursor within grid bounds if (onlyOne) { const currentSel = this._selectionModel.currentSelection()!; if (currentSel.r1 === currentSel.r2 && currentSel.c1 === currentSel.c2) { const dr = direction === 'down' ? 1 : direction === 'up' ? -1 : 0; const dc = direction === 'right' ? 1 : direction === 'left' ? -1 : 0; let newRow = currentSel.r1 + dr; let newColumn = currentSel.c1 + dc; const rowCount = this.dataModel.rowCount('body'); const columnCount = this.dataModel.columnCount('body'); if (newRow >= rowCount) { newRow = 0; newColumn += 1; } else if (newRow === -1) { newRow = rowCount - 1; newColumn -= 1; } if (newColumn >= columnCount) { newColumn = 0; newRow += 1; if (newRow >= rowCount) { newRow = 0; } } else if (newColumn === -1) { newColumn = columnCount - 1; newRow -= 1; if (newRow === -1) { newRow = rowCount - 1; } } this._selectionModel.select({ r1: newRow, c1: newColumn, r2: newRow, c2: newColumn, cursorRow: newRow, cursorColumn: newColumn, clear: 'all' }); return; } } // if there are multiple selections, move cursor // within selection rectangles this._selectionModel.moveCursorWithinSelections(direction); } /** * Scroll the grid to the current cursor position. * * #### Notes * This is a no-op if the cursor is already visible or * if there is no selection model installed on the grid. */ scrollToCursor(): void { // Bail early if there is no selection model. if (!this._selectionModel) { return; } // Fetch the cursor row and column. let row = this._selectionModel.cursorRow; let column = this._selectionModel.cursorColumn; // Scroll to the cursor cell. this.scrollToCell(row, column); } /** * Scroll the viewport by the specified amount. * * @param dx - The X scroll amount. * * @param dy - The Y scroll amount. */ scrollBy(dx: number, dy: number): void { this.scrollTo(this.scrollX + dx, this.scrollY + dy); } /** * Scroll the viewport by one page. * * @param dir - The desired direction of the scroll. */ scrollByPage(dir: 'up' | 'down' | 'left' | 'right'): void { let dx = 0; let dy = 0; switch (dir) { case 'up': dy = -this.pageHeight; break; case 'down': dy = this.pageHeight; break; case 'left': dx = -this.pageWidth; break; case 'right': dx = this.pageWidth; break; default: throw 'unreachable'; } this.scrollTo(this.scrollX + dx, this.scrollY + dy); } /** * Scroll the viewport by one cell-aligned step. * * @param dir - The desired direction of the scroll. */ scrollByStep(dir: 'up' | 'down' | 'left' | 'right'): void { let r: number; let c: number; let x = this.scrollX; let y = this.scrollY; let rows = this._rowSections; let columns = this._columnSections; switch (dir) { case 'up': r = rows.indexOf(y - 1); y = r < 0 ? y : rows.offsetOf(r); break; case 'down': r = rows.indexOf(y); y = r < 0 ? y : rows.offsetOf(r) + rows.sizeOf(r); break; case 'left': c = columns.indexOf(x - 1); x = c < 0 ? x : columns.offsetOf(c); break; case 'right': c = columns.indexOf(x); x = c < 0 ? x : columns.offsetOf(c) + columns.sizeOf(c); break; default: throw 'unreachable'; } this.scrollTo(x, y); } /** * Scroll to the specified offset position. * * @param x - The desired X position. * * @param y - The desired Y position. */ scrollTo(x: number, y: number): void { // Floor and clamp the position to the allowable range. x = Math.max(0, Math.min(Math.floor(x), this.maxScrollX)); y = Math.max(0, Math.min(Math.floor(y), this.maxScrollY)); // Update the scroll bar values with the desired position. this._hScrollBar.value = x; this._vScrollBar.value = y; // Post a scroll request message to the viewport. MessageLoop.postMessage(this._viewport, Private.ScrollRequest); } /** * Get the row count for a particular region in the data grid. * * @param region - The row region of interest. * * @returns The row count for the specified region. */ rowCount(region: DataModel.RowRegion): number { let count: number; if (region === 'body') { count = this._rowSections.count; } else { count = this._columnHeaderSections.count; } return count; } /** * Get the column count for a particular region in the data grid. * * @param region - The column region of interest. * * @returns The column count for the specified region. */ columnCount(region: DataModel.ColumnRegion): number { let count: number; if (region === 'body') { count = this._columnSections.count; } else { count = this._rowHeaderSections.count; } return count; } /** * Get the row at a virtual offset in the data grid. * * @param region - The region which holds the row of interest. * * @param offset - The virtual offset of the row of interest. * * @returns The index of the row, or `-1` if the offset is out of range. * * #### Notes * This method accounts for a stretched last row. */ rowAt(region: DataModel.RowRegion, offset: number): number { // Bail early if the offset is negative. if (offset < 0) { return -1; } // Return early for the column header region. if (region === 'column-header') { return this._columnHeaderSections.indexOf(offset); } // Fetch the index. let index = this._rowSections.indexOf(offset); // Return early if the section is found. if (index >= 0) { return index; } // Bail early if the last row is not stretched. if (!this._stretchLastRow) { return -1; } // Fetch the geometry. let bh = this.bodyHeight; let ph = this.pageHeight; // Bail early if no row stretching is required. if (ph <= bh) { return -1; } // Bail early if the offset is out of bounds. if (offset >= ph) { return -1; } // Otherwise, return the last row. return this._rowSections.count - 1; } /** * Get the column at a virtual offset in the data grid. * * @param region - The region which holds the column of interest. * * @param offset - The virtual offset of the column of interest. * * @returns The index of the column, or `-1` if the offset is out of range. * * #### Notes * This method accounts for a stretched last column. */ columnAt(region: DataModel.ColumnRegion, offset: number): number { if (offset < 0) { return -1; } // Return early for the row header region. if (region === 'row-header') { return this._rowHeaderSections.indexOf(offset); } // Fetch the index. let index = this._columnSections.indexOf(offset); // Return early if the section is found. if (index >= 0) { return index; } // Bail early if the last column is not stretched. if (!this._stretchLastColumn) { return -1; } // Fetch the geometry. let bw = this.bodyWidth; let pw = this.pageWidth; // Bail early if no column stretching is required. if (pw <= bw) { return -1; } // Bail early if the offset is out of bounds. if (offset >= pw) { return -1; } // Otherwise, return the last column. return this._columnSections.count - 1; } /** * Get the offset of a row in the data grid. * * @param region - The region which holds the row of interest. * * @param index - The index of the row of interest. * * @returns The offset of the row, or `-1` if the index is out of range. * * #### Notes * A stretched last row has no effect on the return value. */ rowOffset(region: DataModel.RowRegion, index: number): number { let offset: number; if (region === 'body') { offset = this._rowSections.offsetOf(index); } else { offset = this._columnHeaderSections.offsetOf(index); } return offset; } /** * Get the offset of a column in the data grid. * * @param region - The region which holds the column of interest. * * @param index - The index of the column of interest. * * @returns The offset of the column, or `-1` if the index is out of range. * * #### Notes * A stretched last column has no effect on the return value. */ columnOffset(region: DataModel.ColumnRegion, index: number): number { let offset: number; if (region === 'body') { offset = this._columnSections.offsetOf(index); } else { offset = this._rowHeaderSections.offsetOf(index); } return offset; } /** * Get the size of a row in the data grid. * * @param region - The region which holds the row of interest. * * @param index - The index of the row of interest. * * @returns The size of the row, or `-1` if the index is out of range. * * #### Notes * This method accounts for a stretched last row. */ rowSize(region: DataModel.RowRegion, index: number): number { // Return early for the column header region. if (region === 'column-header') { return this._columnHeaderSections.sizeOf(index); } // Fetch the row size. let size = this._rowSections.sizeOf(index); // Bail early if the index is out of bounds. if (size < 0) { return size; } // Return early if the last row is not stretched. if (!this._stretchLastRow) { return size; } // Return early if its not the last row. if (index < this._rowSections.count - 1) { return size; } // Fetch the geometry. let bh = this.bodyHeight; let ph = this.pageHeight; // Return early if no stretching is needed. if (ph <= bh) { return size; } // Return the adjusted size. return size + (ph - bh); } /** * Get the size of a column in the data grid. * * @param region - The region which holds the column of interest. * * @param index - The index of the column of interest. * * @returns The size of the column, or `-1` if the index is out of range. * * #### Notes * This method accounts for a stretched last column. */ columnSize(region: DataModel.ColumnRegion, index: number): number { // Return early for the row header region. if (region === 'row-header') { return this._rowHeaderSections.sizeOf(index); } // Fetch the column size. let size = this._columnSections.sizeOf(index); // Bail early if the index is out of bounds. if (size < 0) { return size; } // Return early if the last column is not stretched. if (!this._stretchLastColumn) { return size; } // Return early if its not the last column. if (index < this._columnSections.count - 1) { return size; } // Fetch the geometry. let bw = this.bodyWidth; let pw = this.pageWidth; // Return early if no stretching is needed. if (pw <= bw) { return size; } // Return the adjusted size. return size + (pw - bw); } /** * Resize a row in the data grid. * * @param region - The region which holds the row of interest. * * @param index - The index of the row of interest. * * @param size - The desired size of the row. */ resizeRow(region: DataModel.RowRegion, index: number, size: number): void { let msg = new Private.RowResizeRequest(region, index, size); MessageLoop.postMessage(this._viewport, msg); } /** * Resize a column in the data grid. * * @param region - The region which holds the column of interest. * * @param index - The index of the column of interest. * * @param size - The desired size of the column. */ resizeColumn( region: DataModel.ColumnRegion, index: number, size: number ): void { let msg = new Private.ColumnResizeRequest(region, index, size); MessageLoop.postMessage(this._viewport, msg); } /** * Reset modified rows to their default size. * * @param region - The row region of interest. */ resetRows(region: DataModel.RowRegion | 'all'): void { switch (region) { case 'all': this._rowSections.reset(); this._columnHeaderSections.reset(); break; case 'body': this._rowSections.reset(); break; case 'column-header': this._columnHeaderSections.reset(); break; default: throw 'unreachable'; } this.repaintContent(); this.repaintOverlay(); } /** * Reset modified columns to their default size. * * @param region - The column region of interest. */ resetColumns(region: DataModel.ColumnRegion | 'all'): void { switch (region) { case 'all': this._columnSections.reset(); this._rowHeaderSections.reset(); break; case 'body': this._columnSections.reset(); break; case 'row-header': this._rowHeaderSections.reset(); break; default: throw 'unreachable'; } this.repaintContent(); this.repaintOverlay(); } /** * Auto sizes column widths based on their text content. * @param area which area to resize: 'body', 'row-header' or 'all'. * @param padding padding added to resized columns (pixels). * @param numCols specify cap on the number of column resizes (optional). */ fitColumnNames( area: DataGrid.ColumnFitType = 'all', padding: number = 15, numCols?: number ): void { // Attempt resizing only if a data model is present. if (this.dataModel) { // Tracking remaining columns to be resized if numCols arg passed. let colsRemaining = numCols === undefined || numCols < 0 ? undefined : numCols; if (area === 'row-header' || area === 'all') { // Respecting any column resize cap, if one has been passed. if (colsRemaining !== undefined) { const rowColumnCount = this.dataModel.columnCount('row-header'); /* If we have more row-header columns than columns available for resize, resize only remaining columns as per allowance and set remaining resize allowance number to 0. */ if (colsRemaining - rowColumnCount < 0) { this._fitRowColumnHeaders(this.dataModel, padding, colsRemaining); colsRemaining = 0; } else { /* Otherwise the entire row-header column count can be resized. Resize all row-header columns and subtract from remaining column resize allowance. */ this._fitRowColumnHeaders(this.dataModel, padding, rowColumnCount); colsRemaining = colsRemaining - rowColumnCount; } } else { // No column resize cap passed - resizing all columns. this._fitRowColumnHeaders(this.dataModel, padding); } } if (area === 'body' || area === 'all') { // Respecting any column resize cap, if one has been passed. if (colsRemaining !== undefined) { const bodyColumnCount = this.dataModel.columnCount('body'); /* If we have more body columns than columns available for resize, resize only remaining columns as per allowance and set remaining resize allowance number to 0. */ if (colsRemaining - bodyColumnCount < 0) { this._fitBodyColumnHeaders(this.dataModel, padding, colsRemaining); colsRemaining = 0; } else { /* Otherwise the entire body column count can be resized. Resize based on the smallest number between remaining resize allowance and body column count. */ this._fitBodyColumnHeaders( this.dataModel, padding, Math.min(colsRemaining, bodyColumnCount) ); } } else { // No column resize cap passed - resizing all columns. this._fitBodyColumnHeaders(this.dataModel, padding); } } } } /** * Map a client position to local viewport coordinates. * * @param clientX - The client X position of the mouse. * * @param clientY - The client Y position of the mouse. * * @returns The local viewport coordinates for the position. */ mapToLocal(clientX: number, clientY: number): { lx: number; ly: number } { // Fetch the viewport rect. let rect = this._viewport.node.getBoundingClientRect(); // Extract the rect coordinates. let { left, top } = rect; // Round the rect coordinates for sub-pixel positioning. left = Math.floor(left); top = Math.floor(top); // Convert to local coordinates. let lx = clientX - left; let ly = clientY - top; // Return the local coordinates. return { lx, ly }; } /** * Map a client position to virtual grid coordinates. * * @param clientX - The client X position of the mouse. * * @param clientY - The client Y position of the mouse. * * @returns The virtual grid coordinates for the position. */ mapToVirtual(clientX: number, clientY: number): { vx: number; vy: number } { // Convert to local coordiates. let { lx, ly } = this.mapToLocal(clientX, clientY); // Convert to virtual coordinates. let vx = lx + this.scrollX - this.headerWidth; let vy = ly + this.scrollY - this.headerHeight; // Return the local coordinates. return { vx, vy }; } /** * Hit test the viewport for the given client position. * * @param clientX - The client X position of the mouse. * * @param clientY - The client Y position of the mouse. * * @returns The hit test result, or `null` if the client * position is out of bounds. * * #### Notes * This method accounts for a stretched last row and/or column. */ hitTest(clientX: number, clientY: number): DataGrid.HitTestResult { // Convert the mouse position into local coordinates. let { lx, ly } = this.mapToLocal(clientX, clientY); // Fetch the header and body dimensions. let hw = this.headerWidth; let hh = this.headerHeight; let bw = this.bodyWidth; let bh = this.bodyHeight; let ph = this.pageHeight; let pw = this.pageWidth; // Adjust the body width for a stretched last column. if (this._stretchLastColumn && pw > bw) { bw = pw; } // Adjust the body height for a stretched last row. if (this._stretchLastRow && ph > bh) { bh = ph; } // Check for a corner header hit. if (lx >= 0 && lx < hw && ly >= 0 && ly < hh) { // Convert to unscrolled virtual coordinates. let vx = lx; let vy = ly; // Fetch the row and column index. let row = this.rowAt('column-header', vy); let column = this.columnAt('row-header', vx); // Fetch the cell offset position. let ox = this.columnOffset('row-header', column); let oy = this.rowOffset('column-header', row); // Fetch cell width and height. let width = this.columnSize('row-header', column); let height = this.rowSize('column-header', row); // Compute the leading and trailing positions. let x = vx - ox; let y = vy - oy; // Return the hit test result. return { region: 'corner-header', row, column, x, y, width, height }; } // Check for a column header hit. if (ly >= 0 && ly < hh && lx >= 0 && lx < hw + bw) { // Convert to unscrolled virtual coordinates. let vx = lx + this._scrollX - hw; let vy = ly; // Fetch the row and column index. let row = this.rowAt('column-header', vy); let column = this.columnAt('body', vx); // Fetch the cell offset position. let ox = this.columnOffset('body', column); let oy = this.rowOffset('column-header', row); // Fetch the cell width and height. let width = this.columnSize('body', column); let height = this.rowSize('column-header', row); // Compute the leading and trailing positions. let x = vx - ox; let y = vy - oy; // Return the hit test result. return { region: 'column-header', row, column, x, y, width, height }; } // Check for a row header hit. if (lx >= 0 && lx < hw && ly >= 0 && ly < hh + bh) { // Convert to unscrolled virtual coordinates. let vx = lx; let vy = ly + this._scrollY - hh; // Fetch the row and column index. let row = this.rowAt('body', vy); let column = this.columnAt('row-header', vx); // Fetch the cell offset position. let ox = this.columnOffset('row-header', column); let oy = this.rowOffset('body', row); // Fetch the cell width and height. let width = this.columnSize('row-header', column); let height = this.rowSize('body', row); // Compute the leading and trailing positions. let x = vx - ox; let y = vy - oy; // Return the hit test result. return { region: 'row-header', row, column, x, y, width, height }; } // Check for a body hit. if (lx >= hw && lx < hw + bw && ly >= hh && ly < hh + bh) { // Convert to unscrolled virtual coordinates. let vx = lx + this._scrollX - hw; let vy = ly + this._scrollY - hh; // Fetch the row and column index. let row = this.rowAt('body', vy); let column = this.columnAt('body', vx); // Fetch the cell offset position. let ox = this.columnOffset('body', column); let oy = this.rowOffset('body', row); // Fetch the cell width and height. let width = this.columnSize('body', column); let height = this.rowSize('body', row); // Compute the part coordinates. let x = vx - ox; let y = vy - oy; // Return the result. return { region: 'body', row, column, x, y, width, height }; } // Otherwise, it's a void space hit. let row = -1; let column = -1; let x = -1; let y = -1; let width = -1; let height = -1; // Return the hit test result. return { region: 'void', row, column, x, y, width, height }; } /** * Copy the current selection to the system clipboard. * * #### Notes * The grid must have a data model and a selection model. * * The behavior can be configured via `DataGrid.copyConfig`. */ copyToClipboard(): void { // Fetch the data model. let dataModel = this._dataModel; // Bail early if there is no data model. if (!dataModel) { return; } // Fetch the selection model. let selectionModel = this._selectionModel; // Bail early if there is no selection model. if (!selectionModel) { return; } // Coerce the selections to an array. let selections = toArray(selectionModel.selections()); // Bail early if there are no selections. if (selections.length === 0) { return; } // Alert that multiple selections cannot be copied. if (selections.length > 1) { alert('Cannot copy multiple grid selections.'); return; } // Fetch the model counts. let br = dataModel.rowCount('body'); let bc = dataModel.columnCount('body'); // Bail early if there is nothing to copy. if (br === 0 || bc === 0) { return; } // Unpack the selection. let { r1, c1, r2, c2 } = selections[0]; // Clamp the selection to the model bounds. r1 = Math.max(0, Math.min(r1, br - 1)); c1 = Math.max(0, Math.min(c1, bc - 1)); r2 = Math.max(0, Math.min(r2, br - 1)); c2 = Math.max(0, Math.min(c2, bc - 1)); // Ensure the limits are well-orderd. if (r2 < r1) [r1, r2] = [r2, r1]; if (c2 < c1) [c1, c2] = [c2, c1]; // Fetch the header counts. let rhc = dataModel.columnCount('row-header'); let chr = dataModel.rowCount('column-header'); // Unpack the copy config. let separator = this._copyConfig.separator; let format = this._copyConfig.format; let headers = this._copyConfig.headers; let warningThreshold = this._copyConfig.warningThreshold; // Compute the number of cells to be copied. let rowCount = r2 - r1 + 1; let colCount = c2 - c1 + 1; switch (headers) { case 'none': rhc = 0; chr = 0; break; case 'row': chr = 0; colCount += rhc; break; case 'column': rhc = 0; rowCount += chr; break; case 'all': rowCount += chr; colCount += rhc; break; default: throw 'unreachable'; } // Compute the total cell count. let cellCount = rowCount * colCount; // Allow the user to cancel a large copy request. if (cellCount > warningThreshold) { let msg = `Copying ${cellCount} cells may take a while. Continue?`; if (!window.confirm(msg)) { return; } } // Set up the format args. let args = { region: 'body' as DataModel.CellRegion, row: 0, column: 0, value: null as any, metadata: {} as DataModel.Metadata }; // Allocate the array of rows. let rows = new Array(rowCount); // Iterate over the rows. for (let j = 0; j < rowCount; ++j) { // Allocate the array of cells. let cells = new Array(colCount); // Iterate over the columns. for (let i = 0; i < colCount; ++i) { // Set up the format variables. let region: DataModel.CellRegion; let row: number; let column: number; // Populate the format variables. if (j < chr && i < rhc) { region = 'corner-header'; row = j; column = i; } else if (j < chr) { region = 'column-header'; row = j; column = i - rhc + c1; } else if (i < rhc) { region = 'row-header'; row = j - chr + r1; column = i; } else { region = 'body'; row = j - chr + r1; column = i - rhc + c1; } // Populate the format args. args.region = region; args.row = row; args.column = column; args.value = dataModel.data(region, row, column); args.metadata = dataModel.metadata(region, row, column); // Format the cell. cells[i] = format(args); } // Save the row of cells. rows[j] = cells; } // Convert the cells into lines. let lines = rows.map(cells => cells.join(separator)); // Convert the lines into text. let text = lines.join('\n'); // Copy the text to the clipboard. ClipboardExt.copyText(text); } /** * Process a message sent to the widget. * * @param msg - The message sent to the widget. */ processMessage(msg: Message): void { // Ignore child show/hide messages. The data grid controls the // visibility of its children, and will manually dispatch the // fit-request messages as a result of visibility change. if (msg.type === 'child-shown' || msg.type === 'child-hidden') { return; } // Recompute the scroll bar minimums before the layout refits. if (msg.type === 'fit-request') { let vsbLimits = ElementExt.sizeLimits(this._vScrollBar.node); let hsbLimits = ElementExt.sizeLimits(this._hScrollBar.node); this._vScrollBarMinWidth = vsbLimits.minWidth; this._hScrollBarMinHeight = hsbLimits.minHeight; } // Process all other messages as normal. super.processMessage(msg); } /** * Intercept a message sent to a message handler. * * @param handler - The target handler of the message. * * @param msg - The message to be sent to the handler. * * @returns `true` if the message should continue to be processed * as normal, or `false` if processing should cease immediately. */ messageHook(handler: IMessageHandler, msg: Message): boolean { // Process viewport messages. if (handler === this._viewport) { this._processViewportMessage(msg); return true; } // Process horizontal scroll bar messages. if (handler === this._hScrollBar && msg.type === 'activate-request') { this.activate(); return false; } // Process vertical scroll bar messages. if (handler === this._vScrollBar && msg.type === 'activate-request') { this.activate(); return false; } // Ignore all other messages. return true; } /** * Handle the DOM events for the data grid. * * @param event - The DOM event sent to the data grid. * * #### Notes * This method implements the DOM `EventListener` interface and is * called in response to events on the data grid's DOM node. It * should not be called directly by user code. */ handleEvent(event: Event): void { switch (event.type) { case 'keydown': this._evtKeyDown(event as KeyboardEvent); break; case 'mousedown': this._evtMouseDown(event as MouseEvent); break; case 'mousemove': this._evtMouseMove(event as MouseEvent); break; case 'mouseup': this._evtMouseUp(event as MouseEvent); break; case 'dblclick': this._evtMouseDoubleClick(event as MouseEvent); break; case 'mouseleave': this._evtMouseLeave(event as MouseEvent); break; case 'contextmenu': this._evtContextMenu(event as MouseEvent); break; case 'wheel': this._evtWheel(event as WheelEvent); break; case 'resize': this._refreshDPI(); break; } } /** * A message handler invoked on an `'activate-request'` message. */ protected onActivateRequest(msg: Message): void { this.viewport.node.focus({ preventScroll: true }); } /** * A message handler invoked on a `'before-attach'` message. */ protected onBeforeAttach(msg: Message): void { window.addEventListener('resize', this); this.node.addEventListener('wheel', this); this._viewport.node.addEventListener('keydown', this); this._viewport.node.addEventListener('mousedown', this); this._viewport.node.addEventListener('mousemove', this); this._viewport.node.addEventListener('dblclick', this); this._viewport.node.addEventListener('mouseleave', this); this._viewport.node.addEventListener('contextmenu', this); this.repaintContent(); this.repaintOverlay(); } /** * A message handler invoked on an `'after-detach'` message. */ protected onAfterDetach(msg: Message): void { window.removeEventListener('resize', this); this.node.removeEventListener('wheel', this); this._viewport.node.removeEventListener('keydown', this); this._viewport.node.removeEventListener('mousedown', this); this._viewport.node.removeEventListener('mousemove', this); this._viewport.node.removeEventListener('mouseleave', this); this._viewport.node.removeEventListener('dblclick', this); this._viewport.node.removeEventListener('contextmenu', this); this._releaseMouse(); } /** * A message handler invoked on a `'before-show'` message. */ protected onBeforeShow(msg: Message): void { this.repaintContent(); this.repaintOverlay(); } /** * A message handler invoked on a `'resize'` message. */ protected onResize(msg: Widget.ResizeMessage): void { if (this._editorController) { this._editorController.cancel(); } this._syncScrollState(); } /** * Schedule a repaint of all of the grid content. */ protected repaintContent(): void { let msg = new Private.PaintRequest('all', 0, 0, 0, 0); MessageLoop.postMessage(this._viewport, msg); } /** * Schedule a repaint of specific grid content. */ protected repaintRegion( region: DataModel.CellRegion, r1: number, c1: number, r2: number, c2: number ): void { let msg = new Private.PaintRequest(region, r1, c1, r2, c2); MessageLoop.postMessage(this._viewport, msg); } /** * Schedule a repaint of the overlay. */ protected repaintOverlay(): void { MessageLoop.postMessage(this._viewport, Private.OverlayPaintRequest); } /** * Ensure the canvas is at least the specified size. * * This method will retain the valid canvas content. */ private _resizeCanvasIfNeeded(width: number, height: number): void { // Scale the size by the dpi ratio. width = width * this._dpiRatio; height = height * this._dpiRatio; // Compute the maximum canvas size for the given width and height. let maxW = (Math.ceil((width + 1) / 512) + 1) * 512; let maxH = (Math.ceil((height + 1) / 512) + 1) * 512; // Get the current size of the canvas. let curW = this._canvas.width; let curH = this._canvas.height; // Bail early if the canvas size is within bounds. if (curW >= width && curH >= height && curW <= maxW && curH <= maxH) { return; } // Compute the expanded canvas size. let expW = maxW - 512; let expH = maxH - 512; // Set the transforms to the identity matrix. this._canvasGC.setTransform(1, 0, 0, 1, 0, 0); this._bufferGC.setTransform(1, 0, 0, 1, 0, 0); this._overlayGC.setTransform(1, 0, 0, 1, 0, 0); // Resize the buffer if needed. if (curW < width) { this._buffer.width = expW; } else if (curW > maxW) { this._buffer.width = maxW; } // Resize the buffer height if needed. if (curH < height) { this._buffer.height = expH; } else if (curH > maxH) { this._buffer.height = maxH; } // Test whether there is content to blit. let needBlit = curH > 0 && curH > 0 && width > 0 && height > 0; // Copy the valid canvas content into the buffer if needed. if (needBlit) { this._bufferGC.drawImage(this._canvas, 0, 0); } // Resize the canvas width if needed. if (curW < width) { this._canvas.width = expW; this._canvas.style.width = `${expW / this._dpiRatio}px`; } else if (curW > maxW) { this._canvas.width = maxW; this._canvas.style.width = `${maxW / this._dpiRatio}px`; } // Resize the canvas height if needed. if (curH < height) { this._canvas.height = expH; this._canvas.style.height = `${expH / this._dpiRatio}px`; } else if (curH > maxH) { this._canvas.height = maxH; this._canvas.style.height = `${maxH / this._dpiRatio}px`; } // Copy the valid canvas content from the buffer if needed. if (needBlit) { this._canvasGC.drawImage(this._buffer, 0, 0); } // Copy the valid overlay content into the buffer if needed. if (needBlit) { this._bufferGC.drawImage(this._overlay, 0, 0); } // Resize the overlay width if needed. if (curW < width) { this._overlay.width = expW; this._overlay.style.width = `${expW / this._dpiRatio}px`; } else if (curW > maxW) { this._overlay.width = maxW; this._overlay.style.width = `${maxW / this._dpiRatio}px`; } // Resize the overlay height if needed. if (curH < height) { this._overlay.height = expH; this._overlay.style.height = `${expH / this._dpiRatio}px`; } else if (curH > maxH) { this._overlay.height = maxH; this._overlay.style.height = `${maxH / this._dpiRatio}px`; } // Copy the valid overlay content from the buffer if needed. if (needBlit) { this._overlayGC.drawImage(this._buffer, 0, 0); } } /** * Sync the scroll bars and scroll state with the viewport. * * #### Notes * If the visibility of either scroll bar changes, a synchronous * fit-request will be dispatched to the data grid to immediately * resize the viewport. */ private _syncScrollState(): void { // Fetch the viewport dimensions. let bw = this.bodyWidth; let bh = this.bodyHeight; let pw = this.pageWidth; let ph = this.pageHeight; // Get the current scroll bar visibility. let hasVScroll = !this._vScrollBar.isHidden; let hasHScroll = !this._hScrollBar.isHidden; // Get the minimum sizes of the scroll bars. let vsw = this._vScrollBarMinWidth; let hsh = this._hScrollBarMinHeight; // Get the page size as if no scroll bars are visible. let apw = pw + (hasVScroll ? vsw : 0); let aph = ph + (hasHScroll ? hsh : 0); // Test whether scroll bars are needed for the adjusted size. let needVScroll = aph < bh - 1; let needHScroll = apw < bw - 1; // Re-test the horizontal scroll if a vertical scroll is needed. if (needVScroll && !needHScroll) { needHScroll = apw - vsw < bw - 1; } // Re-test the vertical scroll if a horizontal scroll is needed. if (needHScroll && !needVScroll) { needVScroll = aph - hsh < bh - 1; } // If the visibility changes, immediately refit the grid. if (needVScroll !== hasVScroll || needHScroll !== hasHScroll) { this._vScrollBar.setHidden(!needVScroll); this._hScrollBar.setHidden(!needHScroll); this._scrollCorner.setHidden(!needVScroll || !needHScroll); MessageLoop.sendMessage(this, Widget.Msg.FitRequest); } // Update the scroll bar limits. this._vScrollBar.maximum = this.maxScrollY; this._vScrollBar.page = this.pageHeight; this._hScrollBar.maximum = this.maxScrollX; this._hScrollBar.page = this.pageWidth; // Re-clamp the scroll position. this._scrollTo(this._scrollX, this._scrollY); } /** * Sync the viewport to the given scroll position. * * #### Notes * This schedules a full repaint and syncs the scroll state. */ private _syncViewport(): void { this.repaintContent(); this.repaintOverlay(); this._syncScrollState(); } /** * Process a message sent to the viewport */ private _processViewportMessage(msg: Message): void { switch (msg.type) { case 'resize': this._onViewportResize(msg as Widget.ResizeMessage); break; case 'scroll-request': this._onViewportScrollRequest(msg); break; case 'paint-request': this._onViewportPaintRequest(msg as Private.PaintRequest); break; case 'overlay-paint-request': this._onViewportOverlayPaintRequest(msg); break; case 'row-resize-request': this._onViewportRowResizeRequest(msg as Private.RowResizeRequest); break; case 'column-resize-request': this._onViewportColumnResizeRequest(msg as Private.ColumnResizeRequest); break; default: break; } } /** * A message hook invoked on a viewport `'resize'` message. */ private _onViewportResize(msg: Widget.ResizeMessage): void { // Bail early if the viewport is not visible. if (!this._viewport.isVisible) { return; } // Unpack the message data. let { width, height } = msg; // Measure the viewport node if the dimensions are unknown. if (width === -1) { width = this._viewport.node.offsetWidth; } if (height === -1) { height = this._viewport.node.offsetHeight; } // Round the dimensions to the nearest pixel. width = Math.round(width); height = Math.round(height); // Get the current size of the viewport. let oldWidth = this._viewportWidth; let oldHeight = this._viewportHeight; // Updated internal viewport size. this._viewportWidth = width; this._viewportHeight = height; // Resize the canvas if needed. this._resizeCanvasIfNeeded(width, height); // Bail early if there is nothing to paint. if (width === 0 || height === 0) { return; } // Paint the whole grid if the old size was zero. if (oldWidth === 0 || oldHeight === 0) { this.paintContent(0, 0, width, height); this._paintOverlay(); return; } // Paint the right edge as needed. if (this._stretchLastColumn && this.pageWidth > this.bodyWidth) { let bx = this._columnSections.offsetOf(this._columnSections.count - 1); let x = Math.min(this.headerWidth + bx, oldWidth); this.paintContent(x, 0, width - x, height); } else if (width > oldWidth) { this.paintContent(oldWidth, 0, width - oldWidth + 1, height); } // Paint the bottom edge as needed. if (this._stretchLastRow && this.pageHeight > this.bodyHeight) { let by = this._rowSections.offsetOf(this._rowSections.count - 1); let y = Math.min(this.headerHeight + by, oldHeight); this.paintContent(0, y, width, height - y); } else if (height > oldHeight) { this.paintContent(0, oldHeight, width, height - oldHeight + 1); } // Paint the overlay. this._paintOverlay(); } /** * A message hook invoked on a viewport `'scroll-request'` message. */ private _onViewportScrollRequest(msg: Message): void { this._scrollTo(this._hScrollBar.value, this._vScrollBar.value); } /** * A message hook invoked on a viewport `'paint-request'` message. */ private _onViewportPaintRequest(msg: Private.PaintRequest): void { // Bail early if the viewport is not visible. if (!this._viewport.isVisible) { return; } // Bail early if the viewport has zero area. if (this._viewportWidth === 0 || this._viewportHeight === 0) { return; } // Set up the paint limits. let xMin = 0; let yMin = 0; let xMax = this._viewportWidth - 1; let yMax = this._viewportHeight - 1; // Fetch the scroll position. let sx = this._scrollX; let sy = this._scrollY; // Fetch the header dimensions. let hw = this.headerWidth; let hh = this.headerHeight; // Fetch the section lists. let rs = this._rowSections; let cs = this._columnSections; let rhs = this._rowHeaderSections; let chs = this._columnHeaderSections; // Unpack the message data. let { region, r1, c1, r2, c2 } = msg; // Set up the paint variables. let x1: number; let y1: number; let x2: number; let y2: number; // Fill the paint variables based on the paint region. switch (region) { case 'all': x1 = xMin; y1 = yMin; x2 = xMax; y2 = yMax; break; case 'body': r1 = Math.max(0, Math.min(r1, rs.count)); c1 = Math.max(0, Math.min(c1, cs.count)); r2 = Math.max(0, Math.min(r2, rs.count)); c2 = Math.max(0, Math.min(c2, cs.count)); x1 = cs.offsetOf(c1) - sx + hw; y1 = rs.offsetOf(r1) - sy + hh; x2 = cs.extentOf(c2) - sx + hw; y2 = rs.extentOf(r2) - sy + hh; break; case 'row-header': r1 = Math.max(0, Math.min(r1, rs.count)); c1 = Math.max(0, Math.min(c1, rhs.count)); r2 = Math.max(0, Math.min(r2, rs.count)); c2 = Math.max(0, Math.min(c2, rhs.count)); x1 = rhs.offsetOf(c1); y1 = rs.offsetOf(r1) - sy + hh; x2 = rhs.extentOf(c2); y2 = rs.extentOf(r2) - sy + hh; break; case 'column-header': r1 = Math.max(0, Math.min(r1, chs.count)); c1 = Math.max(0, Math.min(c1, cs.count)); r2 = Math.max(0, Math.min(r2, chs.count)); c2 = Math.max(0, Math.min(c2, cs.count)); x1 = cs.offsetOf(c1) - sx + hw; y1 = chs.offsetOf(r1); x2 = cs.extentOf(c2) - sx + hw; y2 = chs.extentOf(r2); break; case 'corner-header': r1 = Math.max(0, Math.min(r1, chs.count)); c1 = Math.max(0, Math.min(c1, rhs.count)); r2 = Math.max(0, Math.min(r2, chs.count)); c2 = Math.max(0, Math.min(c2, rhs.count)); x1 = rhs.offsetOf(c1); y1 = chs.offsetOf(r1); x2 = rhs.extentOf(c2); y2 = chs.extentOf(r2); break; default: throw 'unreachable'; } // Bail early if the dirty rect is outside the bounds. if (x2 < xMin || y2 < yMin || x1 > xMax || y1 > yMax) { return; } // Clamp the dirty rect to the paint bounds. x1 = Math.max(xMin, Math.min(x1, xMax)); y1 = Math.max(yMin, Math.min(y1, yMax)); x2 = Math.max(xMin, Math.min(x2, xMax)); y2 = Math.max(yMin, Math.min(y2, yMax)); // Paint the content of the dirty rect. this.paintContent(x1, y1, x2 - x1 + 1, y2 - y1 + 1); } /** * A message hook invoked on a viewport `'overlay-paint-request'` message. */ private _onViewportOverlayPaintRequest(msg: Message): void { // Bail early if the viewport is not visible. if (!this._viewport.isVisible) { return; } // Bail early if the viewport has zero area. if (this._viewportWidth === 0 || this._viewportHeight === 0) { return; } // Paint the content of the overlay. this._paintOverlay(); } /** * A message hook invoked on a viewport `'row-resize-request'` message. */ private _onViewportRowResizeRequest(msg: Private.RowResizeRequest): void { if (msg.region === 'body') { this._resizeRow(msg.index, msg.size); } else { this._resizeColumnHeader(msg.index, msg.size); } } /** * A message hook invoked on a viewport `'column-resize-request'` message. */ private _onViewportColumnResizeRequest( msg: Private.ColumnResizeRequest ): void { if (msg.region === 'body') { this._resizeColumn(msg.index, msg.size); } else { this._resizeRowHeader(msg.index, msg.size); } } /** * Handle the `thumbMoved` signal from a scroll bar. */ private _onThumbMoved(sender: ScrollBar): void { MessageLoop.postMessage(this._viewport, Private.ScrollRequest); } /** * Handle the `pageRequested` signal from a scroll bar. */ private _onPageRequested( sender: ScrollBar, dir: 'decrement' | 'increment' ): void { if (sender === this._vScrollBar) { this.scrollByPage(dir === 'decrement' ? 'up' : 'down'); } else { this.scrollByPage(dir === 'decrement' ? 'left' : 'right'); } } /** * Handle the `stepRequested` signal from a scroll bar. */ private _onStepRequested( sender: ScrollBar, dir: 'decrement' | 'increment' ): void { if (sender === this._vScrollBar) { this.scrollByStep(dir === 'decrement' ? 'up' : 'down'); } else { this.scrollByStep(dir === 'decrement' ? 'left' : 'right'); } } /** * A signal handler for the data model `changed` signal. */ private _onDataModelChanged( sender: DataModel, args: DataModel.ChangedArgs ): void { switch (args.type) { case 'rows-inserted': this._onRowsInserted(args); break; case 'columns-inserted': this._onColumnsInserted(args); break; case 'rows-removed': this._onRowsRemoved(args); break; case 'columns-removed': this._onColumnsRemoved(args); break; case 'rows-moved': this._onRowsMoved(args); break; case 'columns-moved': this._onColumnsMoved(args); break; case 'cells-changed': this._onCellsChanged(args); break; case 'model-reset': this._onModelReset(args); break; default: throw 'unreachable'; } } /** * A signal handler for the selection model `changed` signal. */ private _onSelectionsChanged(sender: SelectionModel): void { this.repaintOverlay(); } /** * Handle rows being inserted in the data model. */ private _onRowsInserted(args: DataModel.RowsChangedArgs): void { // Unpack the arg data. let { region, index, span } = args; // Bail early if there are no sections to insert. if (span <= 0) { return; } // Look up the relevant section list. let list: SectionList; if (region === 'body') { list = this._rowSections; } else { list = this._columnHeaderSections; } // Insert the span, maintaining the scroll position as needed. if (this._scrollY === this.maxScrollY && this.maxScrollY > 0) { list.insert(index, span); this._scrollY = this.maxScrollY; } else { list.insert(index, span); } // Sync the viewport. this._syncViewport(); } /** * Handle columns being inserted into the data model. */ private _onColumnsInserted(args: DataModel.ColumnsChangedArgs): void { // Unpack the arg data. let { region, index, span } = args; // Bail early if there are no sections to insert. if (span <= 0) { return; } // Look up the relevant section list. let list: SectionList; if (region === 'body') { list = this._columnSections; } else { list = this._rowHeaderSections; } // Insert the span, maintaining the scroll position as needed. if (this._scrollX === this.maxScrollX && this.maxScrollX > 0) { list.insert(index, span); this._scrollX = this.maxScrollX; } else { list.insert(index, span); } // Sync the viewport. this._syncViewport(); } /** * Handle rows being removed from the data model. */ private _onRowsRemoved(args: DataModel.RowsChangedArgs): void { // Unpack the arg data. let { region, index, span } = args; // Bail early if there are no sections to remove. if (span <= 0) { return; } // Look up the relevant section list. let list: SectionList; if (region === 'body') { list = this._rowSections; } else { list = this._columnHeaderSections; } // Bail if the index or is invalid if (index < 0 || index >= list.count) { return; } // Remove the span, maintaining the scroll position as needed. if (this._scrollY === this.maxScrollY && this.maxScrollY > 0) { list.remove(index, span); this._scrollY = this.maxScrollY; } else { list.remove(index, span); } // Sync the viewport. this._syncViewport(); } /** * Handle columns being removed from the data model. */ private _onColumnsRemoved(args: DataModel.ColumnsChangedArgs): void { // Unpack the arg data. let { region, index, span } = args; // Bail early if there are no sections to remove. if (span <= 0) { return; } // Look up the relevant section list. let list: SectionList; if (region === 'body') { list = this._columnSections; } else { list = this._rowHeaderSections; } // Bail if the index or is invalid if (index < 0 || index >= list.count) { return; } // Remove the span, maintaining the scroll position as needed. if (this._scrollX === this.maxScrollX && this.maxScrollX > 0) { list.remove(index, span); this._scrollX = this.maxScrollX; } else { list.remove(index, span); } // Sync the viewport. this._syncViewport(); } /** * Handle rows moving in the data model. */ private _onRowsMoved(args: DataModel.RowsMovedArgs): void { // Unpack the arg data. let { region, index, span, destination } = args; // Bail early if there are no sections to move. if (span <= 0) { return; } // Look up the relevant section list. let list: SectionList; if (region === 'body') { list = this._rowSections; } else { list = this._columnHeaderSections; } // Bail early if the index is out of range. if (index < 0 || index >= list.count) { return; } // Clamp the move span to the limit. span = Math.min(span, list.count - index); // Clamp the destination index to the limit. destination = Math.min(Math.max(0, destination), list.count - span); // Bail early if there is no effective move. if (index === destination) { return; } // Compute the first affected index. let r1 = Math.min(index, destination); // Compute the last affected index. let r2 = Math.max(index + span - 1, destination + span - 1); // Move the sections in the list. list.move(index, span, destination); // Schedule a repaint of the dirty cells. if (region === 'body') { this.repaintRegion('body', r1, 0, r2, Infinity); this.repaintRegion('row-header', r1, 0, r2, Infinity); } else { this.repaintRegion('column-header', r1, 0, r2, Infinity); this.repaintRegion('corner-header', r1, 0, r2, Infinity); } // Sync the viewport. this._syncViewport(); } /** * Handle columns moving in the data model. */ private _onColumnsMoved(args: DataModel.ColumnsMovedArgs): void { // Unpack the arg data. let { region, index, span, destination } = args; // Bail early if there are no sections to move. if (span <= 0) { return; } // Look up the relevant section list. let list: SectionList; if (region === 'body') { list = this._columnSections; } else { list = this._rowHeaderSections; } // Bail early if the index is out of range. if (index < 0 || index >= list.count) { return; } // Clamp the move span to the limit. span = Math.min(span, list.count - index); // Clamp the destination index to the limit. destination = Math.min(Math.max(0, destination), list.count - span); // Bail early if there is no effective move. if (index === destination) { return; } // Move the sections in the list. list.move(index, span, destination); // Compute the first affected index. let c1 = Math.min(index, destination); // Compute the last affected index. let c2 = Math.max(index + span - 1, destination + span - 1); // Schedule a repaint of the dirty cells. if (region === 'body') { this.repaintRegion('body', 0, c1, Infinity, c2); this.repaintRegion('column-header', 0, c1, Infinity, c2); } else { this.repaintRegion('row-header', 0, c1, Infinity, c2); this.repaintRegion('corner-header', 0, c1, Infinity, c2); } // Sync the viewport. this._syncViewport(); } /** * Handle cells changing in the data model. */ private _onCellsChanged(args: DataModel.CellsChangedArgs): void { // Unpack the arg data. let { region, row, column, rowSpan, columnSpan } = args; // Bail early if there are no cells to modify. if (rowSpan <= 0 && columnSpan <= 0) { return; } // Compute the changed cell bounds. let r1 = row; let c1 = column; let r2 = r1 + rowSpan - 1; let c2 = c1 + columnSpan - 1; // Schedule a repaint of the cell content. this.repaintRegion(region, r1, c1, r2, c2); } /** * Handle a full data model reset. */ private _onModelReset(args: DataModel.ModelResetArgs): void { // Look up the various current section counts. let nr = this._rowSections.count; let nc = this._columnSections.count; let nrh = this._rowHeaderSections.count; let nch = this._columnHeaderSections.count; // Compute the delta count for each region. let dr = this._dataModel!.rowCount('body') - nr; let dc = this._dataModel!.columnCount('body') - nc; let drh = this._dataModel!.columnCount('row-header') - nrh; let dch = this._dataModel!.rowCount('column-header') - nch; // Update the row sections, if needed. if (dr > 0) { this._rowSections.insert(nr, dr); } else if (dr < 0) { this._rowSections.remove(nr + dr, -dr); } // Update the column sections, if needed. if (dc > 0) { this._columnSections.insert(nc, dc); } else if (dc < 0) { this._columnSections.remove(nc + dc, -dc); } // Update the row header sections, if needed. if (drh > 0) { this._rowHeaderSections.insert(nrh, drh); } else if (drh < 0) { this._rowHeaderSections.remove(nrh + drh, -drh); } // Update the column header sections, if needed. if (dch > 0) { this._columnHeaderSections.insert(nch, dch); } else if (dch < 0) { this._columnHeaderSections.remove(nch + dch, -dch); } // Sync the viewport. this._syncViewport(); } /** * A signal handler for the renderer map `changed` signal. */ private _onRenderersChanged(): void { this.repaintContent(); } /** * Handle the `'keydown'` event for the data grid. */ private _evtKeyDown(event: KeyboardEvent): void { if (this._mousedown) { event.preventDefault(); event.stopPropagation(); } else if (this._keyHandler) { this._keyHandler.onKeyDown(this, event); } } /** * Handle the `'mousedown'` event for the data grid. */ private _evtMouseDown(event: MouseEvent): void { // Ignore everything except the left mouse button. if (event.button !== 0) { return; } // Activate the grid. this.activate(); // Stop the event propagation. event.preventDefault(); event.stopPropagation(); // Add the extra document listeners. document.addEventListener('keydown', this, true); document.addEventListener('mouseup', this, true); document.addEventListener('mousedown', this, true); document.addEventListener('mousemove', this, true); document.addEventListener('contextmenu', this, true); // Flip the mousedown flag. this._mousedown = true; // Dispatch to the mouse handler. if (this._mouseHandler) { this._mouseHandler.onMouseDown(this, event); } } /** * Handle the `'mousemove'` event for the data grid. */ private _evtMouseMove(event: MouseEvent): void { // Stop the event propagation if the mouse is down. if (this._mousedown) { event.preventDefault(); event.stopPropagation(); } // Bail if there is no mouse handler. if (!this._mouseHandler) { return; } // Dispatch to the mouse handler. if (this._mousedown) { this._mouseHandler.onMouseMove(this, event); } else { this._mouseHandler.onMouseHover(this, event); } } /** * Handle the `'mouseup'` event for the data grid. */ private _evtMouseUp(event: MouseEvent): void { // Ignore everything except the left mouse button. if (event.button !== 0) { return; } // Stop the event propagation. event.preventDefault(); event.stopPropagation(); // Dispatch to the mouse handler. if (this._mouseHandler) { this._mouseHandler.onMouseUp(this, event); } // Release the mouse. this._releaseMouse(); } /** * Handle the `'dblclick'` event for the data grid. */ private _evtMouseDoubleClick(event: MouseEvent): void { // Ignore everything except the left mouse button. if (event.button !== 0) { return; } // Stop the event propagation. event.preventDefault(); event.stopPropagation(); // Dispatch to the mouse handler. if (this._mouseHandler) { this._mouseHandler.onMouseDoubleClick(this, event); } // Release the mouse. this._releaseMouse(); } /** * Handle the `'mouseleave'` event for the data grid. */ private _evtMouseLeave(event: MouseEvent): void { if (this._mousedown) { event.preventDefault(); event.stopPropagation(); } else if (this._mouseHandler) { this._mouseHandler.onMouseLeave(this, event); } } /** * Handle the `'contextmenu'` event for the data grid. */ private _evtContextMenu(event: MouseEvent): void { if (this._mousedown) { event.preventDefault(); event.stopPropagation(); } else if (this._mouseHandler) { this._mouseHandler.onContextMenu(this, event); } } /** * Handle the `'wheel'` event for the data grid. */ private _evtWheel(event: WheelEvent): void { // Ignore the event if `accel` is held. if (Platform.accelKey(event)) { return; } // Bail early if there is no mouse handler. if (!this._mouseHandler) { return; } // Stop the event propagation. event.preventDefault(); event.stopPropagation(); // Dispatch to the mouse handler. this._mouseHandler.onWheel(this, event); } /** * Release the mouse grab. */ private _releaseMouse(): void { // Clear the mousedown flag. this._mousedown = false; // Relase the mouse handler. if (this._mouseHandler) { this._mouseHandler.release(); } // Remove the document listeners. document.removeEventListener('keydown', this, true); document.removeEventListener('mouseup', this, true); document.removeEventListener('mousedown', this, true); document.removeEventListener('mousemove', this, true); document.removeEventListener('contextmenu', this, true); } /** * Refresh the dpi ratio. */ private _refreshDPI(): void { // Get the best integral value for the dpi ratio. let dpiRatio = Math.ceil(window.devicePixelRatio); // Bail early if the computed dpi ratio has not changed. if (this._dpiRatio === dpiRatio) { return; } // Update the internal dpi ratio. this._dpiRatio = dpiRatio; // Schedule a repaint of the content. this.repaintContent(); // Schedule a repaint of the overlay. this.repaintOverlay(); // Update the canvas size for the new dpi ratio. this._resizeCanvasIfNeeded(this._viewportWidth, this._viewportHeight); // Ensure the canvas style is scaled for the new ratio. this._canvas.style.width = `${this._canvas.width / this._dpiRatio}px`; this._canvas.style.height = `${this._canvas.height / this._dpiRatio}px`; // Ensure the overlay style is scaled for the new ratio. this._overlay.style.width = `${this._overlay.width / this._dpiRatio}px`; this._overlay.style.height = `${this._overlay.height / this._dpiRatio}px`; } /** * Resize a row section immediately. */ private _resizeRow(index: number, size: number): void { // Look up the target section list. let list = this._rowSections; // Bail early if the index is out of range. if (index < 0 || index >= list.count) { return; } // Look up the old size of the section. let oldSize = list.sizeOf(index); // Normalize the new size of the section. let newSize = list.clampSize(size); // Bail early if the size does not change. if (oldSize === newSize) { return; } // Resize the section in the list. list.resize(index, newSize); // Get the current size of the viewport. let vw = this._viewportWidth; let vh = this._viewportHeight; // If there is nothing to paint, sync the scroll state. if (!this._viewport.isVisible || vw === 0 || vh === 0) { this._syncScrollState(); return; } // Render entire grid if scrolling merged cells grid const paintEverything = Private.shouldPaintEverything(this._dataModel!); if (paintEverything) { this.paintContent(0, 0, vw, vh); this._paintOverlay(); this._syncScrollState(); return; } // Compute the size delta. let delta = newSize - oldSize; // Look up the column header height. let hh = this.headerHeight; // Compute the viewport offset of the section. let offset = list.offsetOf(index) + hh - this._scrollY; // Bail early if there is nothing to paint. if (hh >= vh || offset >= vh) { this._syncScrollState(); return; } // Update the scroll position if the section is not visible. if (offset + oldSize <= hh) { this._scrollY += delta; this._syncScrollState(); return; } // Compute the paint origin of the section. let pos = Math.max(hh, offset); // Paint from the section onward if it spans the viewport. if (offset + oldSize >= vh || offset + newSize >= vh) { this.paintContent(0, pos, vw, vh - pos); this._paintOverlay(); this._syncScrollState(); return; } // Compute the X blit dimensions. let sx = 0; let sw = vw; let dx = 0; // Compute the Y blit dimensions. let sy: number; let sh: number; let dy: number; if (offset + newSize <= hh) { sy = hh - delta; sh = vh - sy; dy = hh; } else { sy = offset + oldSize; sh = vh - sy; dy = sy + delta; } // Blit the valid content to the destination. this._blitContent(this._canvas, sx, sy, sw, sh, dx, dy); // Repaint the section if needed. if (newSize > 0 && offset + newSize > hh) { this.paintContent(0, pos, vw, offset + newSize - pos); } // Paint the trailing space as needed. if (this._stretchLastRow && this.pageHeight > this.bodyHeight) { let r = this._rowSections.count - 1; let y = hh + this._rowSections.offsetOf(r); this.paintContent(0, y, vw, vh - y); } else if (delta < 0) { this.paintContent(0, vh + delta, vw, -delta); } // Paint the overlay. this._paintOverlay(); // Sync the scroll state. this._syncScrollState(); } /** * Resize a column section immediately. */ private _resizeColumn(index: number, size: number): void { // Look up the target section list. let list = this._columnSections; // Bail early if the index is out of range. if (index < 0 || index >= list.count) { return; } // Look up the old size of the section. let oldSize = list.sizeOf(index); // Normalize the new size of the section. let newSize = list.clampSize(size); // Bail early if the size does not change. if (oldSize === newSize) { return; } // Resize the section in the list. list.resize(index, newSize); // Get the current size of the viewport. let vw = this._viewportWidth; let vh = this._viewportHeight; // If there is nothing to paint, sync the scroll state. if (!this._viewport.isVisible || vw === 0 || vh === 0) { this._syncScrollState(); return; } // Render entire grid if scrolling merged cells grid const paintEverything = Private.shouldPaintEverything(this._dataModel!); if (paintEverything) { this.paintContent(0, 0, vw, vh); this._paintOverlay(); this._syncScrollState(); return; } // Compute the size delta. let delta = newSize - oldSize; // Look up the row header width. let hw = this.headerWidth; // Compute the viewport offset of the section. let offset = list.offsetOf(index) + hw - this._scrollX; // Bail early if there is nothing to paint. if (hw >= vw || offset >= vw) { this._syncScrollState(); return; } // Update the scroll position if the section is not visible. if (offset + oldSize <= hw) { this._scrollX += delta; this._syncScrollState(); return; } // Compute the paint origin of the section. let pos = Math.max(hw, offset); // Paint from the section onward if it spans the viewport. if (offset + oldSize >= vw || offset + newSize >= vw) { this.paintContent(pos, 0, vw - pos, vh); this._paintOverlay(); this._syncScrollState(); return; } // Compute the Y blit dimensions. let sy = 0; let sh = vh; let dy = 0; // Compute the X blit dimensions. let sx: number; let sw: number; let dx: number; if (offset + newSize <= hw) { sx = hw - delta; sw = vw - sx; dx = hw; } else { sx = offset + oldSize; sw = vw - sx; dx = sx + delta; } // Blit the valid content to the destination. this._blitContent(this._canvas, sx, sy, sw, sh, dx, dy); // Repaint the section if needed. if (newSize > 0 && offset + newSize > hw) { this.paintContent(pos, 0, offset + newSize - pos, vh); } // Paint the trailing space as needed. if (this._stretchLastColumn && this.pageWidth > this.bodyWidth) { let c = this._columnSections.count - 1; let x = hw + this._columnSections.offsetOf(c); this.paintContent(x, 0, vw - x, vh); } else if (delta < 0) { this.paintContent(vw + delta, 0, -delta, vh); } // Paint the overlay. this._paintOverlay(); // Sync the scroll state after painting. this._syncScrollState(); } /** * Resize a row header section immediately. */ private _resizeRowHeader(index: number, size: number): void { // Look up the target section list. let list = this._rowHeaderSections; // Bail early if the index is out of range. if (index < 0 || index >= list.count) { return; } // Look up the old size of the section. let oldSize = list.sizeOf(index); // Normalize the new size of the section. let newSize = list.clampSize(size); // Bail early if the size does not change. if (oldSize === newSize) { return; } // Resize the section in the list. list.resize(index, newSize); // Get the current size of the viewport. let vw = this._viewportWidth; let vh = this._viewportHeight; // If there is nothing to paint, sync the scroll state. if (!this._viewport.isVisible || vw === 0 || vh === 0) { this._syncScrollState(); return; } // Render entire grid if scrolling merged cells grid const paintEverything = Private.shouldPaintEverything(this._dataModel!); if (paintEverything) { this.paintContent(0, 0, vw, vh); this._paintOverlay(); this._syncScrollState(); return; } // Compute the size delta. let delta = newSize - oldSize; // Look up the offset of the section. let offset = list.offsetOf(index); // Bail early if the section is fully outside the viewport. if (offset >= vw) { this._syncScrollState(); return; } // Paint the entire tail if the section spans the viewport. if (offset + oldSize >= vw || offset + newSize >= vw) { this.paintContent(offset, 0, vw - offset, vh); this._paintOverlay(); this._syncScrollState(); return; } // Compute the blit content dimensions. let sx = offset + oldSize; let sy = 0; let sw = vw - sx; let sh = vh; let dx = sx + delta; let dy = 0; // Blit the valid content to the destination. this._blitContent(this._canvas, sx, sy, sw, sh, dx, dy); // Repaint the header section if needed. if (newSize > 0) { this.paintContent(offset, 0, newSize, vh); } // Paint the trailing space as needed. if (this._stretchLastColumn && this.pageWidth > this.bodyWidth) { let c = this._columnSections.count - 1; let x = this.headerWidth + this._columnSections.offsetOf(c); this.paintContent(x, 0, vw - x, vh); } else if (delta < 0) { this.paintContent(vw + delta, 0, -delta + 1, vh); } // Paint the overlay. this._paintOverlay(); // Sync the scroll state after painting. this._syncScrollState(); } /** * Resize a column header section immediately. */ private _resizeColumnHeader(index: number, size: number): void { // Look up the target section list. let list = this._columnHeaderSections; // Bail early if the index is out of range. if (index < 0 || index >= list.count) { return; } // Look up the old size of the section. let oldSize = list.sizeOf(index); // Normalize the new size of the section. let newSize = list.clampSize(size); // Bail early if the size does not change. if (oldSize === newSize) { return; } // Resize the section in the list. list.resize(index, newSize); // Get the current size of the viewport. let vw = this._viewportWidth; let vh = this._viewportHeight; // If there is nothing to paint, sync the scroll state. if (!this._viewport.isVisible || vw === 0 || vh === 0) { this._syncScrollState(); return; } // Render entire grid if scrolling merged cells grid const paintEverything = Private.shouldPaintEverything(this._dataModel!); if (paintEverything) { this.paintContent(0, 0, vw, vh); this._paintOverlay(); this._syncScrollState(); return; } // Paint the overlay. this._paintOverlay(); // Compute the size delta. let delta = newSize - oldSize; // Look up the offset of the section. let offset = list.offsetOf(index); // Bail early if the section is fully outside the viewport. if (offset >= vh) { this._syncScrollState(); return; } // Paint the entire tail if the section spans the viewport. if (offset + oldSize >= vh || offset + newSize >= vh) { this.paintContent(0, offset, vw, vh - offset); this._paintOverlay(); this._syncScrollState(); return; } // Compute the blit content dimensions. let sx = 0; let sy = offset + oldSize; let sw = vw; let sh = vh - sy; let dx = 0; let dy = sy + delta; // Blit the valid contents to the destination. this._blitContent(this._canvas, sx, sy, sw, sh, dx, dy); // Repaint the header section if needed. if (newSize > 0) { this.paintContent(0, offset, vw, newSize); } // Paint the trailing space as needed. if (this._stretchLastRow && this.pageHeight > this.bodyHeight) { let r = this._rowSections.count - 1; let y = this.headerHeight + this._rowSections.offsetOf(r); this.paintContent(0, y, vw, vh - y); } else if (delta < 0) { this.paintContent(0, vh + delta, vw, -delta + 1); } // Paint the overlay. this._paintOverlay(); // Sync the scroll state after painting. this._syncScrollState(); } /** * Scroll immediately to the specified offset position. */ private _scrollTo(x: number, y: number): void { // Bail if no data model found. if (!this.dataModel) { return; } // Floor and clamp the position to the allowable range. x = Math.max(0, Math.min(Math.floor(x), this.maxScrollX)); y = Math.max(0, Math.min(Math.floor(y), this.maxScrollY)); // Synchronize the scroll bar values. this._hScrollBar.value = x; this._vScrollBar.value = y; // Compute the delta scroll amount. let dx = x - this._scrollX; let dy = y - this._scrollY; // Bail early if there is no effective scroll. if (dx === 0 && dy === 0) { return; } // Bail early if the viewport is not visible. if (!this._viewport.isVisible) { this._scrollX = x; this._scrollY = y; return; } // Get the current size of the viewport. let width = this._viewportWidth; let height = this._viewportHeight; // Bail early if the viewport is empty. if (width === 0 || height === 0) { this._scrollX = x; this._scrollY = y; return; } // Get the visible content origin. let contentX = this.headerWidth; let contentY = this.headerHeight; // Get the visible content dimensions. let contentWidth = width - contentX; let contentHeight = height - contentY; // Bail early if there is no content to draw. if (contentWidth <= 0 && contentHeight <= 0) { this._scrollX = x; this._scrollY = y; return; } // Compute the area which needs painting for the `dx` scroll. let dxArea = 0; if (dx !== 0 && contentWidth > 0) { if (Math.abs(dx) >= contentWidth) { dxArea = contentWidth * height; } else { dxArea = Math.abs(dx) * height; } } // Compute the area which needs painting for the `dy` scroll. let dyArea = 0; if (dy !== 0 && contentHeight > 0) { if (Math.abs(dy) >= contentHeight) { dyArea = width * contentHeight; } else { dyArea = width * Math.abs(dy); } } // If the area sum is larger than the total, paint everything. if (dxArea + dyArea >= width * height) { this._scrollX = x; this._scrollY = y; this.paintContent(0, 0, width, height); this._paintOverlay(); return; } // Update the internal Y scroll position. this._scrollY = y; // Scroll the Y axis if needed. If the scroll distance exceeds // the visible height, paint everything. Otherwise, blit the // valid content and paint the dirty region. if (dy !== 0 && contentHeight > 0) { if (Math.abs(dy) >= contentHeight) { this.paintContent(0, contentY, width, contentHeight); } else { const x = 0; const y = dy < 0 ? contentY : contentY + dy; const w = width; const h = contentHeight - Math.abs(dy); this._blitContent(this._canvas, x, y, w, h, x, y - dy); this.paintContent( 0, dy < 0 ? contentY : height - dy, width, Math.abs(dy) ); } } // Update the internal X scroll position. this._scrollX = x; // Scroll the X axis if needed. If the scroll distance exceeds // the visible width, paint everything. Otherwise, blit the // valid content and paint the dirty region. if (dx !== 0 && contentWidth > 0) { if (Math.abs(dx) >= contentWidth) { this.paintContent(contentX, 0, contentWidth, height); } else { const x = dx < 0 ? contentX : contentX + dx; const y = 0; const w = contentWidth - Math.abs(dx); const h = height; this._blitContent(this._canvas, x, y, w, h, x - dx, y); this.paintContent( dx < 0 ? contentX : width - dx, 0, Math.abs(dx), height ); } } // Paint the overlay. this._paintOverlay(); } /** * Blit content into the on-screen grid canvas. * * The rect should be expressed in viewport coordinates. * * This automatically accounts for the dpi ratio. */ private _blitContent( source: HTMLCanvasElement, x: number, y: number, w: number, h: number, dx: number, dy: number ): void { // Scale the blit coordinates by the dpi ratio. x *= this._dpiRatio; y *= this._dpiRatio; w *= this._dpiRatio; h *= this._dpiRatio; dx *= this._dpiRatio; dy *= this._dpiRatio; // Save the current gc state. this._canvasGC.save(); // Set the transform to the identity matrix. this._canvasGC.setTransform(1, 0, 0, 1, 0, 0); // Draw the specified content. this._canvasGC.drawImage(source, x, y, w, h, dx, dy, w, h); // Restore the gc state. this._canvasGC.restore(); } /** * Paint the grid content for the given dirty rect. * * The rect should be expressed in valid viewport coordinates. * * This is the primary paint entry point. The individual `_draw*` * methods should not be invoked directly. This method dispatches * to the drawing methods in the correct order. */ protected paintContent(rx: number, ry: number, rw: number, rh: number): void { // Scale the canvas and buffer GC for the dpi ratio. this._canvasGC.setTransform(this._dpiRatio, 0, 0, this._dpiRatio, 0, 0); this._bufferGC.setTransform(this._dpiRatio, 0, 0, this._dpiRatio, 0, 0); // Clear the dirty rect of all content. this._canvasGC.clearRect(rx, ry, rw, rh); // Draw the void region. this._drawVoidRegion(rx, ry, rw, rh); // Draw the body region. this._drawBodyRegion(rx, ry, rw, rh); // Draw the row header region. this._drawRowHeaderRegion(rx, ry, rw, rh); // Draw the column header region. this._drawColumnHeaderRegion(rx, ry, rw, rh); // Draw the corner header region. this.drawCornerHeaderRegion(rx, ry, rw, rh); } /** * Resizes body column headers so their text fits * without clipping or wrapping. * @param dataModel */ private _fitBodyColumnHeaders( dataModel: DataModel, padding: number, numCols?: number ): void { // Get the body column count const bodyColumnCount = numCols === undefined ? dataModel.columnCount('body') : numCols; for (let i = 0; i < bodyColumnCount; i++) { /* if we're working with nested column headers, retrieve the nested levels and iterate on them. */ const numRows = dataModel.rowCount('column-header'); /* Calculate the maximum text width vertically, across all nested rows under a given column number. */ let maxWidth = 0; for (let j = 0; j < numRows; j++) { const cellValue = dataModel.data('column-header', j, i); // Basic CellConfig object to get the renderer for that cell let config = { x: 0, y: 0, width: 0, height: 0, region: 'column-header' as DataModel.CellRegion, row: 0, column: i, value: null as any, metadata: DataModel.emptyMetadata }; // Get the renderer for the given cell const renderer = this.cellRenderers.get(config) as TextRenderer; // Use the canvas context to measure the cell's text width const gc = this.canvasGC; gc.font = CellRenderer.resolveOption(renderer.font, config); const textWidth = gc.measureText(cellValue).width; // Update the maximum width for that column. maxWidth = Math.max(maxWidth, textWidth); } /* Send a resize message with new width for the given column. Using a padding of 15 pixels to leave some room. */ this.resizeColumn('body', i, maxWidth + padding); } } /** * Resizes row header columns so their text fits * without clipping or wrapping. * @param dataModel */ private _fitRowColumnHeaders( dataModel: DataModel, padding: number, numCols?: number ): void { /* if we're working with nested row headers, retrieve the nested levels and iterate on them. */ const rowColumnCount = numCols === undefined ? dataModel.columnCount('row-header') : numCols; for (let i = 0; i < rowColumnCount; i++) { const numCols = dataModel.rowCount('column-header'); /* Calculate the maximum text width vertically, across all nested columns under a given row index. */ let maxWidth = 0; for (let j = 0; j < numCols; j++) { const cellValue = dataModel.data('corner-header', j, i); // Basic CellConfig object to get the renderer for that cell. let config = { x: 0, y: 0, width: 0, height: 0, region: 'column-header' as DataModel.CellRegion, row: 0, column: i, value: null as any, metadata: DataModel.emptyMetadata }; // Get the renderer for the given cell. const renderer = this.cellRenderers.get(config) as TextRenderer; // Use the canvas context to measure the cell's text width const gc = this.canvasGC; gc.font = CellRenderer.resolveOption(renderer.font, config); const textWidth = gc.measureText(cellValue).width; maxWidth = Math.max(maxWidth, textWidth); } /* Send a resize message with new width for the given column. Using a padding of 15 pixels to leave some room. */ this.resizeColumn('row-header', i, maxWidth + padding); } } /** * Paint the overlay content for the entire grid. * * This is the primary overlay paint entry point. The individual * `_draw*` methods should not be invoked directly. This method * dispatches to the drawing methods in the correct order. */ private _paintOverlay(): void { // Scale the overlay GC for the dpi ratio. this._overlayGC.setTransform(this._dpiRatio, 0, 0, this._dpiRatio, 0, 0); // Clear the overlay of all content. this._overlayGC.clearRect(0, 0, this._overlay.width, this._overlay.height); // Draw the body selections. this._drawBodySelections(); // Draw the row header selections. this._drawRowHeaderSelections(); // Draw the column header selections. this._drawColumnHeaderSelections(); // Draw the cursor. this._drawCursor(); // Draw the shadows. this._drawShadows(); } /** * Draw the void region for the dirty rect. */ private _drawVoidRegion( rx: number, ry: number, rw: number, rh: number ): void { // Look up the void color. let color = this._style.voidColor; // Bail if there is no void color. if (!color) { return; } // Fill the dirty rect with the void color. this._canvasGC.fillStyle = color; this._canvasGC.fillRect(rx, ry, rw, rh); } /** * Draw the body region which intersects the dirty rect. */ private _drawBodyRegion( rx: number, ry: number, rw: number, rh: number ): void { // Get the visible content dimensions. let contentW = this._columnSections.length - this._scrollX; let contentH = this._rowSections.length - this._scrollY; // Bail if there is no content to draw. if (contentW <= 0 || contentH <= 0) { return; } // Get the visible content origin. let contentX = this.headerWidth; let contentY = this.headerHeight; // Bail if the dirty rect does not intersect the content area. if (rx + rw <= contentX) { return; } if (ry + rh <= contentY) { return; } if (rx >= contentX + contentW) { return; } if (ry >= contentY + contentH) { return; } // Fetch the geometry. let bh = this.bodyHeight; let bw = this.bodyWidth; let ph = this.pageHeight; let pw = this.pageWidth; // Get the upper and lower bounds of the dirty content area. let x1 = Math.max(rx, contentX); let y1 = Math.max(ry, contentY); let x2 = Math.min(rx + rw - 1, contentX + contentW - 1); let y2 = Math.min(ry + rh - 1, contentY + contentH - 1); // Convert the dirty content bounds into cell bounds. let r1 = this._rowSections.indexOf(y1 - contentY + this._scrollY); let c1 = this._columnSections.indexOf(x1 - contentX + this._scrollX); let r2 = this._rowSections.indexOf(y2 - contentY + this._scrollY); let c2 = this._columnSections.indexOf(x2 - contentX + this._scrollX); // Fetch the max row and column. let maxRow = this._rowSections.count - 1; let maxColumn = this._columnSections.count - 1; // Handle a dirty content area larger than the cell count. if (r2 < 0) { r2 = maxRow; } if (c2 < 0) { c2 = maxColumn; } // Convert the cell bounds back to visible coordinates. let x = this._columnSections.offsetOf(c1) + contentX - this._scrollX; let y = this._rowSections.offsetOf(r1) + contentY - this._scrollY; // Set up the paint region size variables. let width = 0; let height = 0; // Allocate the section sizes arrays. let rowSizes = new Array(r2 - r1 + 1); let columnSizes = new Array(c2 - c1 + 1); // Get the row sizes for the region. for (let j = r1; j <= r2; ++j) { let size = this._rowSections.sizeOf(j); rowSizes[j - r1] = size; height += size; } // Get the column sizes for the region. for (let i = c1; i <= c2; ++i) { let size = this._columnSections.sizeOf(i); columnSizes[i - c1] = size; width += size; } // Adjust the geometry if the last row is streched. if (this._stretchLastRow && ph > bh && r2 === maxRow) { let dh = this.pageHeight - this.bodyHeight; rowSizes[rowSizes.length - 1] += dh; height += dh; y2 += dh; } // Adjust the geometry if the last column is streched. if (this._stretchLastColumn && pw > bw && c2 === maxColumn) { let dw = this.pageWidth - this.bodyWidth; columnSizes[columnSizes.length - 1] += dw; width += dw; x2 += dw; } // Create the paint region object. let rgn: Private.PaintRegion = { region: 'body', xMin: x1, yMin: y1, xMax: x2, yMax: y2, x, y, width, height, row: r1, column: c1, rowSizes, columnSizes }; // Draw the background. this._drawBackground(rgn, this._style.backgroundColor); // Draw the row background. this._drawRowBackground(rgn, this._style.rowBackgroundColor); // Draw the column background. this._drawColumnBackground(rgn, this._style.columnBackgroundColor); // Draw the cell content for the paint region. this._drawCells(rgn); // Draw the horizontal grid lines. this._drawHorizontalGridLines( rgn, this._style.horizontalGridLineColor || this._style.gridLineColor ); // Draw the vertical grid lines. this._drawVerticalGridLines( rgn, this._style.verticalGridLineColor || this._style.gridLineColor ); } /** * Draw the row header region which intersects the dirty rect. */ private _drawRowHeaderRegion( rx: number, ry: number, rw: number, rh: number ): void { // Get the visible content dimensions. let contentW = this.headerWidth; let contentH = this.bodyHeight - this._scrollY; // Bail if there is no content to draw. if (contentW <= 0 || contentH <= 0) { return; } // Get the visible content origin. let contentX = 0; let contentY = this.headerHeight; // Bail if the dirty rect does not intersect the content area. if (rx + rw <= contentX) { return; } if (ry + rh <= contentY) { return; } if (rx >= contentX + contentW) { return; } if (ry >= contentY + contentH) { return; } // Fetch the geometry. let bh = this.bodyHeight; let ph = this.pageHeight; // Get the upper and lower bounds of the dirty content area. let x1 = rx; let y1 = Math.max(ry, contentY); let x2 = Math.min(rx + rw - 1, contentX + contentW - 1); let y2 = Math.min(ry + rh - 1, contentY + contentH - 1); // Convert the dirty content bounds into cell bounds. let r1 = this._rowSections.indexOf(y1 - contentY + this._scrollY); let c1 = this._rowHeaderSections.indexOf(x1); let r2 = this._rowSections.indexOf(y2 - contentY + this._scrollY); let c2 = this._rowHeaderSections.indexOf(x2); // Fetch max row and column. let maxRow = this._rowSections.count - 1; let maxColumn = this._rowHeaderSections.count - 1; // Handle a dirty content area larger than the cell count. if (r2 < 0) { r2 = maxRow; } if (c2 < 0) { c2 = maxColumn; } // Convert the cell bounds back to visible coordinates. let x = this._rowHeaderSections.offsetOf(c1); let y = this._rowSections.offsetOf(r1) + contentY - this._scrollY; // Set up the paint region size variables. let width = 0; let height = 0; // Allocate the section sizes arrays. let rowSizes = new Array(r2 - r1 + 1); let columnSizes = new Array(c2 - c1 + 1); // Get the row sizes for the region. for (let j = r1; j <= r2; ++j) { let size = this._rowSections.sizeOf(j); rowSizes[j - r1] = size; height += size; } // Get the column sizes for the region. for (let i = c1; i <= c2; ++i) { let size = this._rowHeaderSections.sizeOf(i); columnSizes[i - c1] = size; width += size; } // Adjust the geometry if the last row is stretched. if (this._stretchLastRow && ph > bh && r2 === maxRow) { let dh = this.pageHeight - this.bodyHeight; rowSizes[rowSizes.length - 1] += dh; height += dh; y2 += dh; } // Create the paint region object. let rgn: Private.PaintRegion = { region: 'row-header', xMin: x1, yMin: y1, xMax: x2, yMax: y2, x, y, width, height, row: r1, column: c1, rowSizes, columnSizes }; // Draw the background. this._drawBackground(rgn, this._style.headerBackgroundColor); // Draw the cell content for the paint region. this._drawCells(rgn); // Draw the horizontal grid lines. this._drawHorizontalGridLines( rgn, this._style.headerHorizontalGridLineColor || this._style.headerGridLineColor ); // Draw the vertical grid lines. this._drawVerticalGridLines( rgn, this._style.headerVerticalGridLineColor || this._style.headerGridLineColor ); } /** * Draw the column header region which intersects the dirty rect. */ private _drawColumnHeaderRegion( rx: number, ry: number, rw: number, rh: number ): void { // Get the visible content dimensions. let contentW = this.bodyWidth - this._scrollX; let contentH = this.headerHeight; // Bail if there is no content to draw. if (contentW <= 0 || contentH <= 0) { return; } // Get the visible content origin. let contentX = this.headerWidth; let contentY = 0; // Bail if the dirty rect does not intersect the content area. if (rx + rw <= contentX) { return; } if (ry + rh <= contentY) { return; } if (rx >= contentX + contentW) { return; } if (ry >= contentY + contentH) { return; } // Fetch the geometry. let bw = this.bodyWidth; let pw = this.pageWidth; // Get the upper and lower bounds of the dirty content area. let x1 = Math.max(rx, contentX); let y1 = ry; let x2 = Math.min(rx + rw - 1, contentX + contentW - 1); let y2 = Math.min(ry + rh - 1, contentY + contentH - 1); // Convert the dirty content bounds into cell bounds. let r1 = this._columnHeaderSections.indexOf(y1); let c1 = this._columnSections.indexOf(x1 - contentX + this._scrollX); let r2 = this._columnHeaderSections.indexOf(y2); let c2 = this._columnSections.indexOf(x2 - contentX + this._scrollX); // Fetch the max row and column. let maxRow = this._columnHeaderSections.count - 1; let maxColumn = this._columnSections.count - 1; // Handle a dirty content area larger than the cell count. if (r2 < 0) { r2 = maxRow; } if (c2 < 0) { c2 = maxColumn; } // Convert the cell bounds back to visible coordinates. let x = this._columnSections.offsetOf(c1) + contentX - this._scrollX; let y = this._columnHeaderSections.offsetOf(r1); // Set up the paint region size variables. let width = 0; let height = 0; // Allocate the section sizes arrays. let rowSizes = new Array(r2 - r1 + 1); let columnSizes = new Array(c2 - c1 + 1); // Get the row sizes for the region. for (let j = r1; j <= r2; ++j) { let size = this._columnHeaderSections.sizeOf(j); rowSizes[j - r1] = size; height += size; } // Get the column sizes for the region. for (let i = c1; i <= c2; ++i) { let size = this._columnSections.sizeOf(i); columnSizes[i - c1] = size; width += size; } // Adjust the geometry if the last column is stretched. if (this._stretchLastColumn && pw > bw && c2 === maxColumn) { let dw = this.pageWidth - this.bodyWidth; columnSizes[columnSizes.length - 1] += dw; width += dw; x2 += dw; } // Create the paint region object. let rgn: Private.PaintRegion = { region: 'column-header', xMin: x1, yMin: y1, xMax: x2, yMax: y2, x, y, width, height, row: r1, column: c1, rowSizes, columnSizes }; // Draw the background. this._drawBackground(rgn, this._style.headerBackgroundColor); // Draw the cell content for the paint region. this._drawCells(rgn); // Draw the horizontal grid lines. this._drawHorizontalGridLines( rgn, this._style.headerHorizontalGridLineColor || this._style.headerGridLineColor ); // Draw the vertical grid lines. this._drawVerticalGridLines( rgn, this._style.headerVerticalGridLineColor || this._style.headerGridLineColor ); } /** * Draw the corner header region which intersects the dirty rect. */ protected drawCornerHeaderRegion( rx: number, ry: number, rw: number, rh: number ): void { // Get the visible content dimensions. let contentW = this.headerWidth; let contentH = this.headerHeight; // Bail if there is no content to draw. if (contentW <= 0 || contentH <= 0) { return; } // Get the visible content origin. let contentX = 0; let contentY = 0; // Bail if the dirty rect does not intersect the content area. if (rx + rw <= contentX) { return; } if (ry + rh <= contentY) { return; } if (rx >= contentX + contentW) { return; } if (ry >= contentY + contentH) { return; } // Get the upper and lower bounds of the dirty content area. let x1 = rx; let y1 = ry; let x2 = Math.min(rx + rw - 1, contentX + contentW - 1); let y2 = Math.min(ry + rh - 1, contentY + contentH - 1); // Convert the dirty content bounds into cell bounds. let r1 = this._columnHeaderSections.indexOf(y1); let c1 = this._rowHeaderSections.indexOf(x1); let r2 = this._columnHeaderSections.indexOf(y2); let c2 = this._rowHeaderSections.indexOf(x2); // Handle a dirty content area larger than the cell count. if (r2 < 0) { r2 = this._columnHeaderSections.count - 1; } if (c2 < 0) { c2 = this._rowHeaderSections.count - 1; } // Convert the cell bounds back to visible coordinates. let x = this._rowHeaderSections.offsetOf(c1); let y = this._columnHeaderSections.offsetOf(r1); // Set up the paint region size variables. let width = 0; let height = 0; // Allocate the section sizes arrays. let rowSizes = new Array(r2 - r1 + 1); let columnSizes = new Array(c2 - c1 + 1); // Get the row sizes for the region. for (let j = r1; j <= r2; ++j) { let size = this._columnHeaderSections.sizeOf(j); rowSizes[j - r1] = size; height += size; } // Get the column sizes for the region. for (let i = c1; i <= c2; ++i) { let size = this._rowHeaderSections.sizeOf(i); columnSizes[i - c1] = size; width += size; } // Create the paint region object. let rgn: Private.PaintRegion = { region: 'corner-header', xMin: x1, yMin: y1, xMax: x2, yMax: y2, x, y, width, height, row: r1, column: c1, rowSizes, columnSizes }; // Draw the background. this._drawBackground(rgn, this._style.headerBackgroundColor); // Draw the cell content for the paint region. this._drawCells(rgn); // Draw the horizontal grid lines. this._drawHorizontalGridLines( rgn, this._style.headerHorizontalGridLineColor || this._style.headerGridLineColor ); // Draw the vertical grid lines. this._drawVerticalGridLines( rgn, this._style.headerVerticalGridLineColor || this._style.headerGridLineColor ); } /** * Draw the background for the given paint region. */ private _drawBackground( rgn: Private.PaintRegion, color: string | undefined ): void { // Bail if there is no color to draw. if (!color) { return; } // Unpack the region. let { xMin, yMin, xMax, yMax } = rgn; // Fill the region with the specified color. this._canvasGC.fillStyle = color; this._canvasGC.fillRect(xMin, yMin, xMax - xMin + 1, yMax - yMin + 1); } /** * Draw the row background for the given paint region. */ private _drawRowBackground( rgn: Private.PaintRegion, colorFn: ((i: number) => string) | undefined ): void { // Bail if there is no color function. if (!colorFn) { return; } // Compute the X bounds for the row. let x1 = Math.max(rgn.xMin, rgn.x); let x2 = Math.min(rgn.x + rgn.width - 1, rgn.xMax); // Draw the background for the rows in the region. for (let y = rgn.y, j = 0, n = rgn.rowSizes.length; j < n; ++j) { // Fetch the size of the row. let size = rgn.rowSizes[j]; // Skip zero sized rows. if (size === 0) { continue; } // Get the background color for the row. let color = colorFn(rgn.row + j); // Fill the row with the background color if needed. if (color) { let y1 = Math.max(rgn.yMin, y); let y2 = Math.min(y + size - 1, rgn.yMax); this._canvasGC.fillStyle = color; this._canvasGC.fillRect(x1, y1, x2 - x1 + 1, y2 - y1 + 1); } // Increment the running Y coordinate. y += size; } } /** * Draw the column background for the given paint region. */ private _drawColumnBackground( rgn: Private.PaintRegion, colorFn: ((i: number) => string) | undefined ): void { // Bail if there is no color function. if (!colorFn) { return; } // Compute the Y bounds for the column. let y1 = Math.max(rgn.yMin, rgn.y); let y2 = Math.min(rgn.y + rgn.height - 1, rgn.yMax); // Draw the background for the columns in the region. for (let x = rgn.x, i = 0, n = rgn.columnSizes.length; i < n; ++i) { // Fetch the size of the column. let size = rgn.columnSizes[i]; // Skip zero sized columns. if (size === 0) { continue; } // Get the background color for the column. let color = colorFn(rgn.column + i); // Fill the column with the background color if needed. if (color) { let x1 = Math.max(rgn.xMin, x); let x2 = Math.min(x + size - 1, rgn.xMax); this._canvasGC.fillStyle = color; this._canvasGC.fillRect(x1, y1, x2 - x1 + 1, y2 - y1 + 1); } // Increment the running X coordinate. x += size; } } /** * Returns column size * @param region * @param index */ private _getColumnSize(region: DataModel.CellRegion, index: number): number { if (region === 'corner-header') { return this._rowHeaderSections.sizeOf(index); } return this.columnSize(region as DataModel.ColumnRegion, index); } /** * Returns row size * @param region * @param index */ private _getRowSize(region: DataModel.CellRegion, index: number): number { if (region === 'corner-header') { return this._columnHeaderSections.sizeOf(index); } return this.rowSize(region as DataModel.RowRegion, index); } /** * Draw the cells for the given paint region. */ private _drawCells(rgn: Private.PaintRegion): void { // Bail if there is no data model. if (!this._dataModel) { return; } // Determine if the cell intersects with a merged group at row or column let intersectingColumnGroups = CellGroup.getCellGroupsAtColumn( this._dataModel!, rgn.region, rgn.column ); let intersectingRowGroups = CellGroup.getCellGroupsAtRow( this._dataModel!, rgn.region, rgn.row ); // move the bounds of the region if edges of the region are part of a merge group. // after the move, new region contains entirety of the merge groups rgn = JSONExt.deepCopy(rgn); const joinedGroup = CellGroup.joinCellGroupWithMergedCellGroups( this.dataModel!, { r1: rgn.row, r2: rgn.row + rgn.rowSizes.length - 1, c1: rgn.column, c2: rgn.column + rgn.columnSizes.length - 1 }, rgn.region ); for (let r = joinedGroup.r1; r < rgn.row; r++) { const h = this._getRowSize(rgn.region, r); rgn.y -= h; rgn.rowSizes = [h].concat(rgn.rowSizes); } rgn.row = joinedGroup.r1; for (let c = joinedGroup.c1; c < rgn.column; c++) { const w = this._getColumnSize(rgn.region, c); rgn.x -= w; rgn.columnSizes = [w].concat(rgn.columnSizes); } rgn.column = joinedGroup.c1; // Set up the cell config object for rendering. let config = { x: 0, y: 0, width: 0, height: 0, region: rgn.region, row: 0, column: 0, value: null as any, metadata: DataModel.emptyMetadata }; let groupIndex = -1; // Save the buffer gc before wrapping. this._bufferGC.save(); // Wrap the buffer gc for painting the cells. let gc = new GraphicsContext(this._bufferGC); let height = 0; // Loop over the columns in the region. for (let x = rgn.x, i = 0, n = rgn.columnSizes.length; i < n; ++i) { let xOffset = 0; let yOffset = 0; // Fetch the size of the column. let width = rgn.columnSizes[i]; // Skip zero sized columns. if (width === 0) { continue; } xOffset = width; // Compute the column index. let column = rgn.column + i; // Update the config for the current column. config.x = x; config.width = width; config.column = column; // Loop over the rows in the column. for (let y = rgn.y, j = 0, n = rgn.rowSizes.length; j < n; ++j) { // Fetch the size of the row. height = rgn.rowSizes[j]; // Skip zero sized rows. if (height === 0) { continue; } // Compute the row index. let row = rgn.row + j; groupIndex = CellGroup.getGroupIndex( this.dataModel!, config.region, row, column ); yOffset = height; /** * For merged cell regions, only rendering the merged region * if the "parent" cell is the one being painted. Bail otherwise. */ if (groupIndex !== -1) { const group = this.dataModel!.group(config.region, groupIndex)!; if (group.r1 === row && group.c1 === column) { width = 0; for (let c = group.c1; c <= group.c2; c++) { width += this._getColumnSize(config.region, c); } height = 0; for (let r = group.r1; r <= group.r2; r++) { height += this._getRowSize(config.region, r); } } else { y += yOffset; continue; } } else { /** * Reset column width if we're rendering a column-header * which is not part of a merged cell group. */ if (rgn.region == 'column-header') { width = rgn.columnSizes[i]; } } // Clear the buffer rect for the cell. gc.clearRect(x, y, width, height); // Save the GC state. gc.save(); // Get the value for the cell. let value: any; try { value = this._dataModel.data(rgn.region, row, column); } catch (err) { value = undefined; console.error(err); } // Get the metadata for the cell. let metadata: DataModel.Metadata; try { metadata = this._dataModel.metadata(rgn.region, row, column); } catch (err) { metadata = DataModel.emptyMetadata; console.error(err); } // Update the config for the current cell. config.y = y; config.height = height; config.width = width; config.row = row; config.value = value; config.metadata = metadata; // Get the renderer for the cell. let renderer = this._cellRenderers.get(config); // Save the GC state. gc.save(); // Paint the cell into the off-screen buffer. try { renderer.paint(gc, config); } catch (err) { console.error(err); } // Restore the GC state. gc.restore(); // Compute the actual X bounds for the cell. let x1 = Math.max(rgn.xMin, config.x); let x2 = Math.min(config.x + config.width - 1, rgn.xMax); // Compute the actual Y bounds for the cell. let y1 = Math.max(rgn.yMin, config.y); let y2 = Math.min(config.y + config.height - 1, rgn.yMax); if ( intersectingColumnGroups.length !== 0 || intersectingRowGroups.length !== 0 ) { if (x2 > x1 && y2 > y1) { this._blitContent( this._buffer, x1, y1, x2 - x1 + 1, y2 - y1 + 1, x1, y1 ); } } else { this._blitContent( this._buffer, x1, y1, x2 - x1 + 1, y2 - y1 + 1, x1, y1 ); } // Increment the running Y coordinate. y += yOffset; } // Restore the GC state. gc.restore(); // Increment the running X coordinate. x += xOffset; } // Dispose of the wrapped gc. gc.dispose(); // Restore the final buffer gc state. this._bufferGC.restore(); } /** * Draw the horizontal grid lines for the given paint region. */ private _drawHorizontalGridLines( rgn: Private.PaintRegion, color: string | undefined ): void { // Bail if there is no color to draw. if (!color) { return; } // Compute the X bounds for the horizontal lines. let x1 = Math.max(rgn.xMin, rgn.x); // Begin the path for the grid lines. this._canvasGC.beginPath(); // Set the line width for the grid lines. this._canvasGC.lineWidth = 1; // Fetch the geometry. let bh = this.bodyHeight; let ph = this.pageHeight; // Fetch the number of grid lines to be drawn. let n = rgn.rowSizes.length; // Adjust the count down if the last line shouldn't be drawn. if (this._stretchLastRow && ph > bh) { if (rgn.row + n === this._rowSections.count) { n -= 1; } } // Draw the horizontal grid lines. for (let y = rgn.y, j = 0; j < n; ++j) { // Fetch the size of the row. let size = rgn.rowSizes[j]; // Skip zero sized rows. if (size === 0) { continue; } let xStart = 0; let lineStarted = false; let lines = []; let leftCurrent = x1; for (let c = rgn.column; c < rgn.column + rgn.columnSizes.length; c++) { const cIndex = c - rgn.column; const cellUp = [rgn.row + j, c]; const cellDown = [rgn.row + j + 1, c]; if ( CellGroup.areCellsMerged( this.dataModel!, rgn.region, cellUp, cellDown ) ) { if (lineStarted) { lines.push([xStart, leftCurrent]); } lineStarted = false; } else { if (!lineStarted) { lineStarted = true; xStart = leftCurrent; } } leftCurrent += rgn.columnSizes[cIndex]; if (c === rgn.column) { leftCurrent -= rgn.xMin - rgn.x; } } if (lineStarted) { lines.push([xStart, rgn.xMax + 1]); } // Compute the Y position of the line. let pos = y + size - 1; // Draw the line if it's in range of the dirty rect. if (pos >= rgn.yMin && pos <= rgn.yMax) { // Render entire grid if scrolling merged cells grid const extendLines = Private.shouldPaintEverything(this._dataModel!); if (extendLines) { for (const line of lines) { const [x1, x2] = line; this._canvasGC.moveTo(x1, pos + 0.5); this._canvasGC.lineTo(x2, pos + 0.5); } } else { const x2 = Math.min(rgn.x + rgn.width, rgn.xMax + 1); this._canvasGC.moveTo(x1, pos + 0.5); this._canvasGC.lineTo(x2, pos + 0.5); } } // Increment the running Y coordinate. y += size; } // Stroke the lines with the specified color. this._canvasGC.strokeStyle = color; this._canvasGC.stroke(); } /** * Draw the vertical grid lines for the given paint region. */ private _drawVerticalGridLines( rgn: Private.PaintRegion, color: string | undefined ): void { // Bail if there is no color to draw. if (!color) { return; } // Compute the Y bounds for the vertical lines. let y1 = Math.max(rgn.yMin, rgn.y); // Begin the path for the grid lines this._canvasGC.beginPath(); // Set the line width for the grid lines. this._canvasGC.lineWidth = 1; // Fetch the geometry. let bw = this.bodyWidth; let pw = this.pageWidth; // Fetch the number of grid lines to be drawn. let n = rgn.columnSizes.length; // Adjust the count down if the last line shouldn't be drawn. if (this._stretchLastColumn && pw > bw) { if (rgn.column + n === this._columnSections.count) { n -= 1; } } // Draw the vertical grid lines. for (let x = rgn.x, i = 0; i < n; ++i) { // Fetch the size of the column. let size = rgn.columnSizes[i]; // Skip zero sized columns. if (size === 0) { continue; } let yStart = 0; let lineStarted = false; let lines = []; let topCurrent = y1; for (let r = rgn.row; r < rgn.row + rgn.rowSizes.length; r++) { const rIndex = r - rgn.row; const cellLeft = [r, rgn.column + i]; const cellRight = [r, rgn.column + i + 1]; if ( CellGroup.areCellsMerged( this.dataModel!, rgn.region, cellLeft, cellRight ) ) { if (lineStarted) { lines.push([yStart, topCurrent]); } lineStarted = false; } else { if (!lineStarted) { lineStarted = true; yStart = topCurrent; } } topCurrent += rgn.rowSizes[rIndex]; if (r === rgn.row) { topCurrent -= rgn.yMin - rgn.y; } } if (lineStarted) { lines.push([yStart, rgn.yMax + 1]); } // Compute the X position of the line. let pos = x + size - 1; // Draw the line if it's in range of the dirty rect. if (pos >= rgn.xMin && pos <= rgn.xMax) { // Render entire grid if scrolling merged cells grid const extendLines = Private.shouldPaintEverything(this._dataModel!); if (extendLines) { for (const line of lines) { // this._canvasGC.strokeStyle = color; this._canvasGC.moveTo(pos + 0.5, line[0]); this._canvasGC.lineTo(pos + 0.5, line[1]); } } else { let y2 = Math.min(rgn.y + rgn.height, rgn.yMax + 1); this._canvasGC.moveTo(pos + 0.5, y1); this._canvasGC.lineTo(pos + 0.5, y2); } } // Increment the running X coordinate. x += size; } // Stroke the lines with the specified color. this._canvasGC.strokeStyle = color; this._canvasGC.stroke(); } /** * Draw the body selections for the data grid. */ private _drawBodySelections(): void { // Fetch the selection model. let model = this._selectionModel; // Bail early if there are no selections. if (!model || model.isEmpty) { return; } // Fetch the selection colors. let fill = this._style.selectionFillColor; let stroke = this._style.selectionBorderColor; // Bail early if there is nothing to draw. if (!fill && !stroke) { return; } // Fetch the scroll geometry. let sx = this._scrollX; let sy = this._scrollY; // Get the first visible cell of the grid. let r1 = this._rowSections.indexOf(sy); let c1 = this._columnSections.indexOf(sx); // Bail early if there are no visible cells. if (r1 < 0 || c1 < 0) { return; } // Fetch the extra geometry. let bw = this.bodyWidth; let bh = this.bodyHeight; let pw = this.pageWidth; let ph = this.pageHeight; let hw = this.headerWidth; let hh = this.headerHeight; // Get the last visible cell of the grid. let r2 = this._rowSections.indexOf(sy + ph); let c2 = this._columnSections.indexOf(sx + pw); // Fetch the max row and column. let maxRow = this._rowSections.count - 1; let maxColumn = this._columnSections.count - 1; // Clamp the last cell if the void space is visible. r2 = r2 < 0 ? maxRow : r2; c2 = c2 < 0 ? maxColumn : c2; // Fetch the overlay gc. let gc = this._overlayGC; // Save the gc state. gc.save(); // Set up the body clipping rect. gc.beginPath(); gc.rect(hw, hh, pw, ph); gc.clip(); // Set up the gc style. if (fill) { gc.fillStyle = fill; } if (stroke) { gc.strokeStyle = stroke; gc.lineWidth = 1; } // Iterate over the selections. let it = model.selections(); let s: SelectionModel.Selection | undefined; while ((s = it.next()) !== undefined) { // Skip the section if it's not visible. if (s.r1 < r1 && s.r2 < r1) { continue; } if (s.r1 > r2 && s.r2 > r2) { continue; } if (s.c1 < c1 && s.c2 < c1) { continue; } if (s.c1 > c2 && s.c2 > c2) { continue; } // Clamp the cell to the model bounds. let sr1 = Math.max(0, Math.min(s.r1, maxRow)); let sc1 = Math.max(0, Math.min(s.c1, maxColumn)); let sr2 = Math.max(0, Math.min(s.r2, maxRow)); let sc2 = Math.max(0, Math.min(s.c2, maxColumn)); // Swap index order if needed. let tmp: number; if (sr1 > sr2) { tmp = sr1; sr1 = sr2; sr2 = tmp; } if (sc1 > sc2) { tmp = sc1; sc1 = sc2; sc2 = tmp; } const joinedGroup = CellGroup.joinCellGroupWithMergedCellGroups( this.dataModel!, { r1: sr1, r2: sr2, c1: sc1, c2: sc2 }, 'body' ); sr1 = joinedGroup.r1; sr2 = joinedGroup.r2; sc1 = joinedGroup.c1; sc2 = joinedGroup.c2; // Convert to pixel coordinates. let x1 = this._columnSections.offsetOf(sc1) - sx + hw; let y1 = this._rowSections.offsetOf(sr1) - sy + hh; let x2 = this._columnSections.extentOf(sc2) - sx + hw; let y2 = this._rowSections.extentOf(sr2) - sy + hh; // Adjust the trailing X coordinate for column stretch. if (this._stretchLastColumn && pw > bw && sc2 === maxColumn) { x2 = hw + pw - 1; } // Adjust the trailing Y coordinate for row stretch. if (this._stretchLastRow && ph > bh && sr2 === maxRow) { y2 = hh + ph - 1; } // Clamp the bounds to just outside of the clipping rect. x1 = Math.max(hw - 1, x1); y1 = Math.max(hh - 1, y1); x2 = Math.min(hw + pw + 1, x2); y2 = Math.min(hh + ph + 1, y2); // Skip zero sized ranges. if (x2 < x1 || y2 < y1) { continue; } // Fill the rect if needed. if (fill) { gc.fillRect(x1, y1, x2 - x1 + 1, y2 - y1 + 1); } // Stroke the rect if needed. if (stroke) { gc.strokeRect(x1 - 0.5, y1 - 0.5, x2 - x1 + 1, y2 - y1 + 1); } } // Restore the gc state. gc.restore(); } /** * Draw the row header selections for the data grid. */ private _drawRowHeaderSelections(): void { // Fetch the selection model. let model = this._selectionModel; // Bail early if there are no selections or if the selectionMode is the entire column. if (!model || model.isEmpty || model.selectionMode == 'column') { return; } // Bail early if the row headers are not visible. if (this.headerWidth === 0 || this.pageHeight === 0) { return; } // Fetch the selection colors. let fill = this._style.headerSelectionFillColor; let stroke = this._style.headerSelectionBorderColor; // Bail early if there is nothing to draw. if (!fill && !stroke) { return; } // Fetch common geometry. let sy = this._scrollY; let bh = this.bodyHeight; let ph = this.pageHeight; let hw = this.headerWidth; let hh = this.headerHeight; let rs = this._rowSections; // Fetch the overlay gc. let gc = this._overlayGC; // Save the gc state. gc.save(); // Set up the header clipping rect. gc.beginPath(); gc.rect(0, hh, hw, ph); gc.clip(); // Set up the gc style. if (fill) { gc.fillStyle = fill; } if (stroke) { gc.strokeStyle = stroke; gc.lineWidth = 1; } // Fetch the max row. let maxRow = rs.count - 1; // Fetch the visible rows. let r1 = rs.indexOf(sy); let r2 = rs.indexOf(sy + ph - 1); r2 = r2 < 0 ? maxRow : r2; // Iterate over the visible rows. for (let j = r1; j <= r2; ++j) { // Skip rows which aren't selected. if (!model.isRowSelected(j)) { continue; } // Get the dimensions of the row. let y = rs.offsetOf(j) - sy + hh; let h = rs.sizeOf(j); // Adjust the height for row stretch. if (this._stretchLastRow && ph > bh && j === maxRow) { h = hh + ph - y; } // Skip zero sized rows. if (h === 0) { continue; } // Fill the rect if needed. if (fill) { gc.fillRect(0, y, hw, h); } // Draw the border if needed. if (stroke) { gc.beginPath(); gc.moveTo(hw - 0.5, y - 1); gc.lineTo(hw - 0.5, y + h); gc.stroke(); } } // Restore the gc state. gc.restore(); } /** * Draw the column header selections for the data grid. */ private _drawColumnHeaderSelections(): void { // Fetch the selection model. let model = this._selectionModel; // Bail early if there are no selections or if the selectionMode is the entire row if (!model || model.isEmpty || model.selectionMode == 'row') { return; } // Bail early if the column headers are not visible. if (this.headerHeight === 0 || this.pageWidth === 0) { return; } // Fetch the selection colors. let fill = this._style.headerSelectionFillColor; let stroke = this._style.headerSelectionBorderColor; // Bail early if there is nothing to draw. if (!fill && !stroke) { return; } // Fetch common geometry. let sx = this._scrollX; let bw = this.bodyWidth; let pw = this.pageWidth; let hw = this.headerWidth; let hh = this.headerHeight; let cs = this._columnSections; // Fetch the overlay gc. let gc = this._overlayGC; // Save the gc state. gc.save(); // Set up the header clipping rect. gc.beginPath(); gc.rect(hw, 0, pw, hh); gc.clip(); // Set up the gc style. if (fill) { gc.fillStyle = fill; } if (stroke) { gc.strokeStyle = stroke; gc.lineWidth = 1; } // Fetch the max column. let maxCol = cs.count - 1; // Fetch the visible columns. let c1 = cs.indexOf(sx); let c2 = cs.indexOf(sx + pw - 1); c2 = c2 < 0 ? maxCol : c2; // Iterate over the visible columns. for (let i = c1; i <= c2; ++i) { // Skip columns which aren't selected. if (!model.isColumnSelected(i)) { continue; } // Get the dimensions of the column. let x = cs.offsetOf(i) - sx + hw; let w = cs.sizeOf(i); // Adjust the width for column stretch. if (this._stretchLastColumn && pw > bw && i === maxCol) { w = hw + pw - x; } // Skip zero sized columns. if (w === 0) { continue; } // Fill the rect if needed. if (fill) { gc.fillRect(x, 0, w, hh); } // Draw the border if needed. if (stroke) { gc.beginPath(); gc.moveTo(x - 1, hh - 0.5); gc.lineTo(x + w, hh - 0.5); gc.stroke(); } } // Restore the gc state. gc.restore(); } /** * Draw the overlay cursor for the data grid. */ private _drawCursor(): void { // Fetch the selection model. let model = this._selectionModel; // Bail early if there is no cursor. if (!model || model.isEmpty || model.selectionMode !== 'cell') { return; } // Extract the style information. let fill = this._style.cursorFillColor; let stroke = this._style.cursorBorderColor; // Bail early if there is nothing to draw. if (!fill && !stroke) { return; } // Fetch the cursor location. let startRow = model.cursorRow; let startColumn = model.cursorColumn; // Fetch the max row and column. let maxRow = this._rowSections.count - 1; let maxColumn = this._columnSections.count - 1; // Bail early if the cursor is out of bounds. if (startRow < 0 || startRow > maxRow) { return; } if (startColumn < 0 || startColumn > maxColumn) { return; } let endRow = startRow; let endColumn = startColumn; const joinedGroup = CellGroup.joinCellGroupWithMergedCellGroups( this.dataModel!, { r1: startRow, r2: endRow, c1: startColumn, c2: endColumn }, 'body' ); startRow = joinedGroup.r1; endRow = joinedGroup.r2; startColumn = joinedGroup.c1; endColumn = joinedGroup.c2; // Fetch geometry. let sx = this._scrollX; let sy = this._scrollY; let bw = this.bodyWidth; let bh = this.bodyHeight; let pw = this.pageWidth; let ph = this.pageHeight; let hw = this.headerWidth; let hh = this.headerHeight; let vw = this._viewportWidth; let vh = this._viewportHeight; // Get the cursor bounds in viewport coordinates. let x1 = this._columnSections.offsetOf(startColumn) - sx + hw; let x2 = this._columnSections.extentOf(endColumn) - sx + hw; let y1 = this._rowSections.offsetOf(startRow) - sy + hh; let y2 = this._rowSections.extentOf(endRow) - sy + hh; // Adjust the trailing X coordinate for column stretch. if (this._stretchLastColumn && pw > bw && startColumn === maxColumn) { x2 = vw - 1; } // Adjust the trailing Y coordinate for row stretch. if (this._stretchLastRow && ph > bh && startRow === maxRow) { y2 = vh - 1; } // Skip zero sized cursors. if (x2 < x1 || y2 < y1) { return; } // Bail early if the cursor is off the screen. if (x1 - 1 >= vw || y1 - 1 >= vh || x2 + 1 < hw || y2 + 1 < hh) { return; } // Fetch the overlay gc. let gc = this._overlayGC; // Save the gc state. gc.save(); // Set up the body clipping rect. gc.beginPath(); gc.rect(hw, hh, pw, ph); gc.clip(); // Clear any existing overlay content. gc.clearRect(x1, y1, x2 - x1 + 1, y2 - y1 + 1); // Fill the cursor rect if needed. if (fill) { // Set up the fill style. gc.fillStyle = fill; // Fill the cursor rect. gc.fillRect(x1, y1, x2 - x1 + 1, y2 - y1 + 1); } // Stroke the cursor border if needed. if (stroke) { // Set up the stroke style. gc.strokeStyle = stroke; gc.lineWidth = 2; // Stroke the cursor rect. gc.strokeRect(x1, y1, x2 - x1, y2 - y1); } // Restore the gc state. gc.restore(); } /** * Draw the overlay shadows for the data grid. */ private _drawShadows(): void { // Fetch the scroll shadow from the style. let shadow = this._style.scrollShadow; // Bail early if there is no shadow to draw. if (!shadow) { return; } // Fetch the scroll position. let sx = this._scrollX; let sy = this._scrollY; // Fetch maximum scroll position. let sxMax = this.maxScrollX; let syMax = this.maxScrollY; // Fetch the header width and height. let hw = this.headerWidth; let hh = this.headerHeight; // Fetch the page width and height. let pw = this.pageWidth; let ph = this.pageHeight; // Fetch the viewport width and height. let vw = this._viewportWidth; let vh = this._viewportHeight; // Fetch the body width and height. let bw = this.bodyWidth; let bh = this.bodyHeight; // Adjust the body size for row and column stretch. if (this._stretchLastRow && ph > bh) { bh = ph; } if (this._stretchLastColumn && pw > bw) { bw = pw; } // Fetch the gc object. let gc = this._overlayGC; // Save the gc state. gc.save(); // Draw the column header shadow if needed. if (sy > 0) { // Set up the gradient coordinates. let x0 = 0; let y0 = hh; let x1 = 0; let y1 = y0 + shadow.size; // Create the gradient object. let grad = gc.createLinearGradient(x0, y0, x1, y1); // Set the gradient stops. grad.addColorStop(0, shadow.color1); grad.addColorStop(0.5, shadow.color2); grad.addColorStop(1, shadow.color3); // Set up the rect coordinates. let x = 0; let y = hh; let w = hw + Math.min(pw, bw - sx); let h = shadow.size; // Fill the shadow rect with the fill style. gc.fillStyle = grad; gc.fillRect(x, y, w, h); } // Draw the row header shadow if needed. if (sx > 0) { // Set up the gradient coordinates. let x0 = hw; let y0 = 0; let x1 = x0 + shadow.size; let y1 = 0; // Create the gradient object. let grad = gc.createLinearGradient(x0, y0, x1, y1); // Set the gradient stops. grad.addColorStop(0, shadow.color1); grad.addColorStop(0.5, shadow.color2); grad.addColorStop(1, shadow.color3); // Set up the rect coordinates. let x = hw; let y = 0; let w = shadow.size; let h = hh + Math.min(ph, bh - sy); // Fill the shadow rect with the fill style. gc.fillStyle = grad; gc.fillRect(x, y, w, h); } // Draw the column footer shadow if needed. if (sy < syMax) { // Set up the gradient coordinates. let x0 = 0; let y0 = vh; let x1 = 0; let y1 = vh - shadow.size; // Create the gradient object. let grad = gc.createLinearGradient(x0, y0, x1, y1); // Set the gradient stops. grad.addColorStop(0, shadow.color1); grad.addColorStop(0.5, shadow.color2); grad.addColorStop(1, shadow.color3); // Set up the rect coordinates. let x = 0; let y = vh - shadow.size; let w = hw + Math.min(pw, bw - sx); let h = shadow.size; // Fill the shadow rect with the fill style. gc.fillStyle = grad; gc.fillRect(x, y, w, h); } // Draw the row footer shadow if needed. if (sx < sxMax) { // Set up the gradient coordinates. let x0 = vw; let y0 = 0; let x1 = vw - shadow.size; let y1 = 0; // Create the gradient object. let grad = gc.createLinearGradient(x0, y0, x1, y1); // Set the gradient stops. grad.addColorStop(0, shadow.color1); grad.addColorStop(0.5, shadow.color2); grad.addColorStop(1, shadow.color3); // Set up the rect coordinates. let x = vw - shadow.size; let y = 0; let w = shadow.size; let h = hh + Math.min(ph, bh - sy); // Fill the shadow rect with the fill style. gc.fillStyle = grad; gc.fillRect(x, y, w, h); } // Restore the gc state. gc.restore(); } private _viewport: Widget; private _vScrollBar: ScrollBar; private _hScrollBar: ScrollBar; private _scrollCorner: Widget; private _scrollX = 0; private _scrollY = 0; private _viewportWidth = 0; private _viewportHeight = 0; private _mousedown = false; private _keyHandler: DataGrid.IKeyHandler | null = null; private _mouseHandler: DataGrid.IMouseHandler | null = null; private _vScrollBarMinWidth = 0; private _hScrollBarMinHeight = 0; private _dpiRatio = Math.ceil(window.devicePixelRatio); private _canvas: HTMLCanvasElement; private _buffer: HTMLCanvasElement; private _overlay: HTMLCanvasElement; private _canvasGC: CanvasRenderingContext2D; private _bufferGC: CanvasRenderingContext2D; private _overlayGC: CanvasRenderingContext2D; private _rowSections: SectionList; private _columnSections: SectionList; private _rowHeaderSections: SectionList; private _columnHeaderSections: SectionList; private _dataModel: DataModel | null = null; private _selectionModel: SelectionModel | null = null; private _stretchLastRow: boolean; private _stretchLastColumn: boolean; private _style: DataGrid.Style; private _cellRenderers: RendererMap; private _copyConfig: DataGrid.CopyConfig; private _headerVisibility: DataGrid.HeaderVisibility; private _editorController: ICellEditorController | null; private _editingEnabled: boolean = false; } /** * The namespace for the `DataGrid` class statics. */ export namespace DataGrid { /** * An object which defines the style for a data grid. * * #### Notes * All style colors support the full CSS color syntax. */ export type Style = { /** * The void color for the data grid. * * This is the base fill color for the entire data grid. */ readonly voidColor?: string; /** * The background color for the body cells. * * This color is layered on top of the `voidColor`. */ readonly backgroundColor?: string; /** * A function which returns the background color for a row. * * This color is layered on top of the `backgroundColor` and can * be used to implement "zebra striping" of the grid rows. */ readonly rowBackgroundColor?: (index: number) => string; /** * A function which returns the background color for a column. * * This color is layered on top of the `backgroundColor` and can * be used to implement "zebra striping" of the grid columns. */ readonly columnBackgroundColor?: (index: number) => string; /** * The color for the grid lines of the body cells. * * The grid lines are draw on top of the cell contents. */ readonly gridLineColor?: string; /** * The color for the vertical grid lines of the body cells. * * This overrides the `gridLineColor` option. */ readonly verticalGridLineColor?: string; /** * The color for the horizontal grid lines of the body cells. * * This overrides the `gridLineColor` option. */ readonly horizontalGridLineColor?: string; /** * The background color for the header cells. * * This color is layered on top of the `voidColor`. */ readonly headerBackgroundColor?: string; /** * The color for the grid lines of the header cells. * * The grid lines are draw on top of the cell contents. */ readonly headerGridLineColor?: string; /** * The color for the vertical grid lines of the header cells. * * This overrides the `headerGridLineColor` option. */ readonly headerVerticalGridLineColor?: string; /** * The color for the horizontal grid lines of the header cells. * * This overrides the `headerGridLineColor` option. */ readonly headerHorizontalGridLineColor?: string; /** * The fill color for a selection. */ readonly selectionFillColor?: string; /** * The border color for a selection. */ readonly selectionBorderColor?: string; /** * The fill color for the cursor. */ readonly cursorFillColor?: string; /** * The border color for the cursor. */ readonly cursorBorderColor?: string; /** * The fill color for a header selection. */ readonly headerSelectionFillColor?: string; /** * The border color for a header selection. */ readonly headerSelectionBorderColor?: string; /** * The drop shadow effect when the grid is scrolled. */ readonly scrollShadow?: { /** * The size of the shadow, in pixels. */ readonly size: number; /** * The first color stop for the shadow. */ readonly color1: string; /** * The second color stop for the shadow. */ readonly color2: string; /** * The third color stop for the shadow. */ readonly color3: string; }; }; /** * An object which defines the default sizes for a data grid. */ export type DefaultSizes = { /** * The default height of a row. */ readonly rowHeight: number; /** * The default width of a column. */ readonly columnWidth: number; /** * The default width of a row header. */ readonly rowHeaderWidth: number; /** * The default height of a column header. */ readonly columnHeaderHeight: number; }; /** * An object which defines the minimum sizes for a data grid. */ export type MinimumSizes = { /** * The minimum height of a row. */ readonly rowHeight: number; /** * The minimum width of a column. */ readonly columnWidth: number; /** * The minimum width of a row header. */ readonly rowHeaderWidth: number; /** * The minimum height of a column header. */ readonly columnHeaderHeight: number; }; /** * A type alias for the supported header visibility modes. */ export type HeaderVisibility = 'all' | 'row' | 'column' | 'none'; /** * A type alias for the supported column auto resize modes. */ export type ColumnFitType = 'all' | 'row-header' | 'body'; /** * A type alias for the arguments to a copy format function. */ export type CopyFormatArgs = { /** * The cell region for the value. */ region: DataModel.CellRegion; /** * The row index of the value. */ row: number; /** * The column index of the value. */ column: number; /** * The value for the cell. */ value: any; /** * The metadata for the cell. */ metadata: DataModel.Metadata; }; /** * A type alias for a copy format function. */ export type CopyFormatFunc = (args: CopyFormatArgs) => string; /** * A type alias for the data grid copy config. */ export type CopyConfig = { /** * The separator to use between values. */ readonly separator: string; /** * The headers to included in the copied output. */ readonly headers: 'none' | 'row' | 'column' | 'all'; /** * The function for formatting the data values. */ readonly format: CopyFormatFunc; /** * The cell count threshold for a copy to be considered "large". */ readonly warningThreshold: number; }; /** * An options object for initializing a data grid. */ export interface IOptions { /** * The style for the data grid. * * The default is `DataGrid.defaultStyle`. */ style?: Style; /** * The default sizes for the data grid. * * The default is `DataGrid.defaultSizes`. */ defaultSizes?: DefaultSizes; /** * The minimum sizes for the data grid. * * The default is `DataGrid.minimumSizes`. */ minimumSizes?: MinimumSizes; /** * The header visibility for the data grid. * * The default is `'all'`. */ headerVisibility?: HeaderVisibility; /** * The cell renderer map for the data grid. * * The default is an empty renderer map. */ cellRenderers?: RendererMap; /** * The default cell renderer for the data grid. * * The default is a new `TextRenderer`. */ defaultRenderer?: CellRenderer; /** * The copy configuration data for the grid. * * The default is `DataGrid.defaultCopyConfig`. */ copyConfig?: CopyConfig; /** * Whether to stretch the last row of the grid. * * The default is `false`. */ stretchLastRow?: boolean; /** * Whether to stretch the last column of the grid. * * The default is `false`. */ stretchLastColumn?: boolean; } /** * An object which handles keydown events for the data grid. */ export interface IKeyHandler extends IDisposable { /** * Handle the key down event for the data grid. * * @param grid - The data grid of interest. * * @param event - The keydown event of interest. * * #### Notes * This will not be called if the mouse button is pressed. */ onKeyDown(grid: DataGrid, event: KeyboardEvent): void; } /** * An object which handles mouse events for the data grid. */ export interface IMouseHandler extends IDisposable { /** * Release any resources acquired during a mouse press. * * #### Notes * This method is called when the mouse should be released * independent of a mouseup event, such as an early detach. */ release(): void; /** * Handle the mouse hover event for the data grid. * * @param grid - The data grid of interest. * * @param event - The mouse hover event of interest. */ onMouseHover(grid: DataGrid, event: MouseEvent): void; /** * Handle the mouse leave event for the data grid. * * @param grid - The data grid of interest. * * @param event - The mouse hover event of interest. */ onMouseLeave(grid: DataGrid, event: MouseEvent): void; /** * Handle the mouse down event for the data grid. * * @param grid - The data grid of interest. * * @param event - The mouse down event of interest. */ onMouseDown(grid: DataGrid, event: MouseEvent): void; /** * Handle the mouse move event for the data grid. * * @param grid - The data grid of interest. * * @param event - The mouse move event of interest. */ onMouseMove(grid: DataGrid, event: MouseEvent): void; /** * Handle the mouse up event for the data grid. * * @param grid - The data grid of interest. * * @param event - The mouse up event of interest. */ onMouseUp(grid: DataGrid, event: MouseEvent): void; /** * Handle the mouse double click event for the data grid. * * @param grid - The data grid of interest. * * @param event - The mouse double click event of interest. */ onMouseDoubleClick(grid: DataGrid, event: MouseEvent): void; /** * Handle the context menu event for the data grid. * * @param grid - The data grid of interest. * * @param event - The context menu event of interest. */ onContextMenu(grid: DataGrid, event: MouseEvent): void; /** * Handle the wheel event for the data grid. * * @param grid - The data grid of interest. * * @param event - The wheel event of interest. */ onWheel(grid: DataGrid, event: WheelEvent): void; } /** * An object which holds the result of a grid hit test. */ export type HitTestResult = { /** * The region of the data grid that was hit. */ readonly region: DataModel.CellRegion | 'void'; /** * The row index of the cell that was hit. * * This is `-1` for the `void` region. */ readonly row: number; /** * The column index of the cell that was hit. * * This is `-1` for the `void` region. */ readonly column: number; /** * The X coordinate of the mouse in cell coordinates. * * This is `-1` for the `void` region. */ readonly x: number; /** * The Y coordinate of the mouse in cell coordinates. * * This is `-1` for the `void` region. */ readonly y: number; /** * The width of the cell. * * This is `-1` for the `void` region. */ readonly width: number; /** * The height of the cell. * * This is `-1` for the `void` region. */ readonly height: number; }; /** * A generic format function for the copy handler. * * @param args - The format args for the function. * * @returns The string representation of the value. * * #### Notes * This function uses `String()` to coerce a value to a string. */ export function copyFormatGeneric(args: CopyFormatArgs): string { if (args.value === null || args.value === undefined) { return ''; } return String(args.value); } /** * The default theme for a data grid. */ export const defaultStyle: Style = { voidColor: '#F3F3F3', backgroundColor: '#FFFFFF', gridLineColor: 'rgba(20, 20, 20, 0.15)', headerBackgroundColor: '#F3F3F3', headerGridLineColor: 'rgba(20, 20, 20, 0.25)', selectionFillColor: 'rgba(49, 119, 229, 0.2)', selectionBorderColor: 'rgba(0, 107, 247, 1.0)', cursorBorderColor: 'rgba(0, 107, 247, 1.0)', headerSelectionFillColor: 'rgba(20, 20, 20, 0.1)', headerSelectionBorderColor: 'rgba(0, 107, 247, 1.0)', scrollShadow: { size: 10, color1: 'rgba(0, 0, 0, 0.20)', color2: 'rgba(0, 0, 0, 0.05)', color3: 'rgba(0, 0, 0, 0.00)' } }; /** * The default sizes for a data grid. */ export const defaultSizes: DefaultSizes = { rowHeight: 20, columnWidth: 64, rowHeaderWidth: 64, columnHeaderHeight: 20 }; /** * The default minimum sizes for a data grid. */ export const minimumSizes: MinimumSizes = { rowHeight: 20, columnWidth: 10, rowHeaderWidth: 10, columnHeaderHeight: 20 }; /** * The default copy config for a data grid. */ export const defaultCopyConfig: CopyConfig = { separator: '\t', format: copyFormatGeneric, headers: 'none', warningThreshold: 1e6 }; } /** * The namespace for the module implementation details. */ namespace Private { /** * A singleton `scroll-request` conflatable message. */ export const ScrollRequest = new ConflatableMessage('scroll-request'); /** * A singleton `overlay-paint-request` conflatable message. */ export const OverlayPaintRequest = new ConflatableMessage( 'overlay-paint-request' ); /** * Create a new zero-sized canvas element. */ export function createCanvas(): HTMLCanvasElement { let canvas = document.createElement('canvas'); canvas.width = 0; canvas.height = 0; return canvas; } /** * A function to check whether the entire grid should be rendered * when dealing with merged cell regions. * @param dataModel grid's data model. * @returns boolean. */ export function shouldPaintEverything(dataModel: DataModel): boolean { const colGroups = CellGroup.getCellGroupsAtRegion( dataModel!, 'column-header' ); const rowHeaderGroups = CellGroup.getCellGroupsAtRegion( dataModel!, 'row-header' ); const cornerHeaderGroups = CellGroup.getCellGroupsAtRegion( dataModel!, 'corner-header' ); const bodyGroups = CellGroup.getCellGroupsAtRegion(dataModel!, 'body'); return ( colGroups.length > 0 || rowHeaderGroups.length > 0 || cornerHeaderGroups.length > 0 || bodyGroups.length > 0 ); } /** * Checks whether a given regions has merged cells in it. * @param dataModel grid's data model. * @param region the paint region to be checked. * @returns boolean. */ export function regionHasMergedCells( dataModel: DataModel, region: DataModel.CellRegion ): boolean { const regionGroups = CellGroup.getCellGroupsAtRegion(dataModel!, region); return regionGroups.length > 0; } /** * An object which represents a region to be painted. */ export type PaintRegion = { /** * The min X coordinate the of the dirty viewport rect. * * #### Notes * The data grid must not draw outside of this boundary. */ xMin: number; /** * The min Y coordinate the of the dirty viewport rect. * * #### Notes * The data grid must not draw outside of this boundary. */ yMin: number; /** * The max X coordinate the of the dirty viewport rect. * * #### Notes * The data grid must not draw outside of this boundary. */ xMax: number; /** * The max Y coordinate the of the dirty viewport rect. * * #### Notes * The data grid must not draw outside of this boundary. */ yMax: number; /** * The X coordinate the of the region, in viewport coordinates. * * #### Notes * This is aligned to the first cell boundary. */ x: number; /** * The Y coordinate the of the region, in viewport coordinates. * * #### Notes * This is aligned to the first cell boundary. */ y: number; /** * The total width of the region. * * #### Notes * This is aligned to the cell boundaries. */ width: number; /** * The total height of the region. * * #### Notes * This is aligned to the cell boundaries. */ height: number; /** * The cell region being painted. */ region: DataModel.CellRegion; /** * The row index of the first cell in the region. */ row: number; /** * The column index of the first cell in the region. */ column: number; /** * The row sizes for the rows in the region. */ rowSizes: number[]; /** * The column sizes for the columns in the region. */ columnSizes: number[]; }; /** * A conflatable message which merges dirty paint regions. */ export class PaintRequest extends ConflatableMessage { /** * Construct a new paint request messages. * * @param region - The cell region for the paint. * * @param r1 - The top-left row of the dirty region. * * @param c1 - The top-left column of the dirty region. * * @param r2 - The bottom-right row of the dirty region. * * @param c2 - The bottom-right column of the dirty region. */ constructor( region: DataModel.CellRegion | 'all', r1: number, c1: number, r2: number, c2: number ) { super('paint-request'); this._region = region; this._r1 = r1; this._c1 = c1; this._r2 = r2; this._c2 = c2; } /** * The cell region for the paint. */ get region(): DataModel.CellRegion | 'all' { return this._region; } /** * The top-left row of the dirty region. */ get r1(): number { return this._r1; } /** * The top-left column of the dirty region. */ get c1(): number { return this._c1; } /** * The bottom-right row of the dirty region. */ get r2(): number { return this._r2; } /** * The bottom-right column of the dirty region. */ get c2(): number { return this._c2; } /** * Conflate this message with another paint request. */ conflate(other: PaintRequest): boolean { // Bail early if the request is already painting everything. if (this._region === 'all') { return true; } // Any region can conflate with the `'all'` region. if (other._region === 'all') { this._region = 'all'; return true; } // Otherwise, do not conflate with a different region. if (this._region !== other._region) { return false; } // Conflate the region to the total boundary. this._r1 = Math.min(this._r1, other._r1); this._c1 = Math.min(this._c1, other._c1); this._r2 = Math.max(this._r2, other._r2); this._c2 = Math.max(this._c2, other._c2); return true; } private _region: DataModel.CellRegion | 'all'; private _r1: number; private _c1: number; private _r2: number; private _c2: number; } /** * A conflatable message for resizing rows. */ export class RowResizeRequest extends ConflatableMessage { /** * Construct a new row resize request. * * @param region - The row region which holds the section. * * @param index - The index of row in the region. * * @param size - The target size of the section. */ constructor(region: DataModel.RowRegion, index: number, size: number) { super('row-resize-request'); this._region = region; this._index = index; this._size = size; } /** * The row region which holds the section. */ get region(): DataModel.RowRegion { return this._region; } /** * The index of the row in the region. */ get index(): number { return this._index; } /** * The target size of the section. */ get size(): number { return this._size; } /** * Conflate this message with another row resize request. */ conflate(other: RowResizeRequest): boolean { if (this._region !== other._region || this._index !== other._index) { return false; } this._size = other._size; return true; } private _region: DataModel.RowRegion; private _index: number; private _size: number; } /** * A conflatable message for resizing columns. */ export class ColumnResizeRequest extends ConflatableMessage { /** * Construct a new column resize request. * * @param region - The column region which holds the section. * * @param index - The index of column in the region. * * @param size - The target size of the section. */ constructor(region: DataModel.ColumnRegion, index: number, size: number) { super('column-resize-request'); this._region = region; this._index = index; this._size = size; } /** * The column region which holds the section. */ get region(): DataModel.ColumnRegion { return this._region; } /** * The index of the column in the region. */ get index(): number { return this._index; } /** * The target size of the section. */ get size(): number { return this._size; } /** * Conflate this message with another column resize request. */ conflate(other: ColumnResizeRequest): boolean { if (this._region !== other._region || this._index !== other._index) { return false; } this._size = other._size; return true; } private _region: DataModel.ColumnRegion; private _index: number; private _size: number; } } lumino-2021.12.13/packages/datagrid/src/datamodel.ts000066400000000000000000000224211415564225700220500ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2019, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { ISignal, Signal } from '@lumino/signaling'; import { CellGroup } from './cellgroup'; /** * An object which provides the data for a data grid. * * #### Notes * If the predefined data models are insufficient for a particular use * case, a custom model can be defined which derives from this class. */ export abstract class DataModel { /** * A signal emitted when the data model has changed. */ get changed(): ISignal { return this._changed; } /** * Get the row count for a region in the data model. * * @param region - The row region of interest. * * @returns - The row count for the region. * * #### Notes * This method is called often, and so should be efficient. */ abstract rowCount(region: DataModel.RowRegion): number; /** * Get the column count for a region in the data model. * * @param region - The column region of interest. * * @returns - The column count for the region. * * #### Notes * This method is called often, and so should be efficient. */ abstract columnCount(region: DataModel.ColumnRegion): number; /** * Get the data value for a cell in the data model. * * @param region - The cell region of interest. * * @param row - The row index of the cell of interest. * * @param column - The column index of the cell of interest. * * @returns The data value for the specified cell. * * #### Notes * The returned data should be treated as immutable. * * This method is called often, and so should be efficient. */ abstract data(region: DataModel.CellRegion, row: number, column: number): any; /** * Get the count of merged cell groups pertaining to a given * cell region. * @param region the target cell region. */ groupCount(region: DataModel.CellRegion): number { return 0; } /** * Get the metadata for a cell in the data model. * * @param region - The cell region of interest. * * @param row - The row index of the cell of interest. * * @param column - The column index of the cell of interest. * * @returns The metadata for the specified cell. * * #### Notes * The returned metadata should be treated as immutable. * * This method is called often, and so should be efficient. * * The default implementation returns `{}`. */ metadata( region: DataModel.CellRegion, row: number, column: number ): DataModel.Metadata { return DataModel.emptyMetadata; } /** * Get the merged cell group corresponding to a region and index number. * @param region the cell region of cell group. * @param groupIndex the group index of the cell group. * @returns a cell group. */ group(region: DataModel.CellRegion, groupIndex: number): CellGroup | null { return null; } /** * Emit the `changed` signal for the data model. * * #### Notes * Subclasses should call this method whenever the data model has * changed so that attached data grids can update themselves. */ protected emitChanged(args: DataModel.ChangedArgs): void { this._changed.emit(args); } private _changed = new Signal(this); } /** * An object which provides the mutable data for a data grid. * * #### Notes * This object is an extension to `DataModel` and it only adds ability to * change data for cells. */ export abstract class MutableDataModel extends DataModel { /** * Set the data value for a cell in the data model. * * @param region - The cell region of interest. * * @param row - The row index of the cell of interest. * * @param column - The column index of the cell of interest. * * @returns true if succeeds, false otherwise. * */ abstract setData( region: DataModel.CellRegion, row: number, column: number, value: any ): boolean; } /** * The namespace for the `DataModel` class statics. */ export namespace DataModel { /** * A type alias for the data model row regions. */ export type RowRegion = 'body' | 'column-header'; /** * A type alias for the data model column regions. */ export type ColumnRegion = 'body' | 'row-header'; /** * A type alias for the data model cell regions. */ export type CellRegion = | 'body' | 'row-header' | 'column-header' | 'corner-header'; /** * The metadata for a column in a data model. */ export type Metadata = { [key: string]: any }; /** * A singleton empty metadata object. */ export const emptyMetadata: Metadata = Object.freeze({}); /** * An arguments object for the `changed` signal. * * #### Notes * Data models should emit the `changed` signal with this args object * type when rows are inserted or removed. */ export type RowsChangedArgs = { /** * The discriminated type of the args object. */ readonly type: 'rows-inserted' | 'rows-removed'; /** * The region which contains the modified rows. */ readonly region: RowRegion; /** * The index of the first modified row. */ readonly index: number; /** * The number of modified rows. */ readonly span: number; }; /** * An arguments object for the `changed` signal. * * #### Notes * Data models should emit the `changed` signal with this args object * type when columns are inserted or removed. */ export type ColumnsChangedArgs = { /** * The discriminated type of the args object. */ readonly type: 'columns-inserted' | 'columns-removed'; /** * The region which contains the modified columns. */ readonly region: ColumnRegion; /** * The index of the first modified column. */ readonly index: number; /** * The number of modified columns. */ readonly span: number; }; /** * An arguments object for the `changed` signal. * * #### Notes * Data models should emit the `changed` signal with this args object * type when rows are moved. */ export type RowsMovedArgs = { /** * The discriminated type of the args object. */ readonly type: 'rows-moved'; /** * The region which contains the modified rows. */ readonly region: RowRegion; /** * The starting index of the first modified row. */ readonly index: number; /** * The number of modified rows. */ readonly span: number; /** * The ending index of the first modified row. */ readonly destination: number; }; /** * An arguments object for the `changed` signal. * * #### Notes * Data models should emit the `changed` signal with this args object * type when columns are moved. */ export type ColumnsMovedArgs = { /** * The discriminated type of the args object. */ readonly type: 'columns-moved'; /** * The region which contains the modified columns. */ readonly region: ColumnRegion; /** * The starting index of the first modified column. */ readonly index: number; /** * The number of modified columns. */ readonly span: number; /** * The ending index of the first modified column. */ readonly destination: number; }; /** * An arguments object for the `changed` signal. * * #### Notes * Data models should emit the `changed` signal with this args object * type when cells are changed in-place. */ export type CellsChangedArgs = { /** * The discriminated type of the args object. */ readonly type: 'cells-changed'; /** * The region which contains the modified cells. */ readonly region: CellRegion; /** * The row index of the first modified cell. */ readonly row: number; /** * The column index of the first modified cell. */ readonly column: number; /** * The number of rows in the modified cell range. */ readonly rowSpan: number; /** * The number of columns in the modified cell range. */ readonly columnSpan: number; }; /** * An arguments object for the `changed` signal. * * #### Notes * Data models should emit the `changed` signal with this args object * type when the model has changed in a fashion that cannot be easily * expressed by the other args object types. * * This is the "big hammer" approach, and will cause any associated * data grid to perform a full reset. The other changed args types * should be used whenever possible. */ export type ModelResetArgs = { /** * The discriminated type of the args object. */ readonly type: 'model-reset'; }; /** * A type alias for the args objects of the `changed` signal. */ export type ChangedArgs = | RowsChangedArgs | ColumnsChangedArgs | RowsMovedArgs | ColumnsMovedArgs | CellsChangedArgs | ModelResetArgs; } lumino-2021.12.13/packages/datagrid/src/graphicscontext.ts000066400000000000000000000400201415564225700233160ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2019, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { IDisposable } from '@lumino/disposable'; /** * A thin caching wrapper around a 2D canvas rendering context. * * #### Notes * This class is mostly a transparent wrapper around a canvas rendering * context which improves performance when writing context state. * * For best performance, avoid reading state from the `gc`. Writes are * cached based on the previously written value. * * Unless otherwise specified, the API and semantics of this class are * identical to the builtin 2D canvas rendering context: * https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D * * The wrapped canvas context should not be manipulated externally * until the wrapping `GraphicsContext` object is disposed. */ export class GraphicsContext implements IDisposable { /** * Create a new graphics context object. * * @param context - The 2D canvas rendering context to wrap. */ constructor(context: CanvasRenderingContext2D) { this._context = context; this._state = Private.State.create(context); } dispose(): void { // Bail if the gc is already disposed. if (this._disposed) { return; } // Mark the gc as disposed. this._disposed = true; // Pop any unrestored saves. while (this._state.next) { this._state = this._state.next; this._context.restore(); } } get isDisposed(): boolean { return this._disposed; } get fillStyle(): string | CanvasGradient | CanvasPattern { return this._context.fillStyle; } set fillStyle(value: string | CanvasGradient | CanvasPattern) { if (this._state.fillStyle !== value) { this._state.fillStyle = value; this._context.fillStyle = value; } } get strokeStyle(): string | CanvasGradient | CanvasPattern { return this._context.strokeStyle; } set strokeStyle(value: string | CanvasGradient | CanvasPattern) { if (this._state.strokeStyle !== value) { this._state.strokeStyle = value; this._context.strokeStyle = value; } } get font(): string { return this._context.font; } set font(value: string) { if (this._state.font !== value) { this._state.font = value; this._context.font = value; } } get textAlign(): CanvasTextAlign { return this._context.textAlign; } set textAlign(value: CanvasTextAlign) { if (this._state.textAlign !== value) { this._state.textAlign = value; this._context.textAlign = value; } } get textBaseline(): CanvasTextBaseline { return this._context.textBaseline; } set textBaseline(value: CanvasTextBaseline) { if (this._state.textBaseline !== value) { this._state.textBaseline = value; this._context.textBaseline = value; } } get lineCap(): CanvasLineCap { return this._context.lineCap; } set lineCap(value: CanvasLineCap) { if (this._state.lineCap !== value) { this._state.lineCap = value; this._context.lineCap = value; } } get lineDashOffset(): number { return this._context.lineDashOffset; } set lineDashOffset(value: number) { if (this._state.lineDashOffset !== value) { this._state.lineDashOffset = value; this._context.lineDashOffset = value; } } get lineJoin(): CanvasLineJoin { return this._context.lineJoin; } set lineJoin(value: CanvasLineJoin) { if (this._state.lineJoin !== value) { this._state.lineJoin = value; this._context.lineJoin = value; } } get lineWidth(): number { return this._context.lineWidth; } set lineWidth(value: number) { if (this._state.lineWidth !== value) { this._state.lineWidth = value; this._context.lineWidth = value; } } get miterLimit(): number { return this._context.miterLimit; } set miterLimit(value: number) { if (this._state.miterLimit !== value) { this._state.miterLimit = value; this._context.miterLimit = value; } } get shadowBlur(): number { return this._context.shadowBlur; } set shadowBlur(value: number) { if (this._state.shadowBlur !== value) { this._state.shadowBlur = value; this._context.shadowBlur = value; } } get shadowColor(): string { return this._context.shadowColor; } set shadowColor(value: string) { if (this._state.shadowColor !== value) { this._state.shadowColor = value; this._context.shadowColor = value; } } get shadowOffsetX(): number { return this._context.shadowOffsetX; } set shadowOffsetX(value: number) { if (this._state.shadowOffsetX !== value) { this._state.shadowOffsetX = value; this._context.shadowOffsetX = value; } } get shadowOffsetY(): number { return this._context.shadowOffsetY; } set shadowOffsetY(value: number) { if (this._state.shadowOffsetY !== value) { this._state.shadowOffsetY = value; this._context.shadowOffsetY = value; } } get imageSmoothingEnabled(): boolean { return this._context.imageSmoothingEnabled; } set imageSmoothingEnabled(value: boolean) { if (this._state.imageSmoothingEnabled !== value) { this._state.imageSmoothingEnabled = value; this._context.imageSmoothingEnabled = value; } } get globalAlpha(): number { return this._context.globalAlpha; } set globalAlpha(value: number) { if (this._state.globalAlpha !== value) { this._state.globalAlpha = value; this._context.globalAlpha = value; } } get globalCompositeOperation(): string { return this._context.globalCompositeOperation; } set globalCompositeOperation(value: string) { if (this._state.globalCompositeOperation !== value) { this._state.globalCompositeOperation = value; this._context.globalCompositeOperation = value; } } getLineDash(): number[] { return this._context.getLineDash(); } setLineDash(segments: number[]): void { this._context.setLineDash(segments); } rotate(angle: number): void { this._context.rotate(angle); } scale(x: number, y: number): void { this._context.scale(x, y); } transform( m11: number, m12: number, m21: number, m22: number, dx: number, dy: number ): void { this._context.transform(m11, m12, m21, m22, dx, dy); } translate(x: number, y: number): void { this._context.translate(x, y); } setTransform( m11: number, m12: number, m21: number, m22: number, dx: number, dy: number ): void { this._context.setTransform(m11, m12, m21, m22, dx, dy); } save(): void { // Clone an push the current state to the stack. this._state = Private.State.push(this._state); // Save the wrapped context state. this._context.save(); } restore(): void { // Bail if there is no state to restore. if (!this._state.next) { return; } // Pop the saved state from the stack. this._state = Private.State.pop(this._state); // Restore the wrapped context state. this._context.restore(); } beginPath(): void { return this._context.beginPath(); } closePath(): void { this._context.closePath(); } isPointInPath(x: number, y: number, fillRule?: CanvasFillRule): boolean { let result: boolean; if (arguments.length === 2) { result = this._context.isPointInPath(x, y); } else { result = this._context.isPointInPath(x, y, fillRule); } return result; } arc( x: number, y: number, radius: number, startAngle: number, endAngle: number, anticlockwise?: boolean ): void { if (arguments.length === 5) { this._context.arc(x, y, radius, startAngle, endAngle); } else { this._context.arc(x, y, radius, startAngle, endAngle, anticlockwise); } } arcTo(x1: number, y1: number, x2: number, y2: number, radius: number): void { this._context.arcTo(x1, y1, x2, y2, radius); } bezierCurveTo( cp1x: number, cp1y: number, cp2x: number, cp2y: number, x: number, y: number ): void { this._context.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y); } ellipse( x: number, y: number, radiusX: number, radiusY: number, rotation: number, startAngle: number, endAngle: number, anticlockwise?: boolean ): void { if (arguments.length === 7) { this._context.ellipse( x, y, radiusX, radiusY, rotation, startAngle, endAngle ); } else { this._context.ellipse( x, y, radiusX, radiusY, rotation, startAngle, endAngle, anticlockwise ); } } lineTo(x: number, y: number): void { this._context.lineTo(x, y); } moveTo(x: number, y: number): void { this._context.moveTo(x, y); } quadraticCurveTo(cpx: number, cpy: number, x: number, y: number): void { this._context.quadraticCurveTo(cpx, cpy, x, y); } rect(x: number, y: number, w: number, h: number): void { this._context.rect(x, y, w, h); } clip(fillRule?: CanvasFillRule): void { if (arguments.length === 0) { this._context.clip(); } else { this._context.clip(fillRule); } } fill(fillRule?: CanvasFillRule): void { if (arguments.length === 0) { this._context.fill(); } else { this._context.fill(fillRule); } } stroke(): void { this._context.stroke(); } clearRect(x: number, y: number, w: number, h: number): void { return this._context.clearRect(x, y, w, h); } fillRect(x: number, y: number, w: number, h: number): void { this._context.fillRect(x, y, w, h); } fillText(text: string, x: number, y: number, maxWidth?: number): void { if (arguments.length === 3) { this._context.fillText(text, x, y); } else { this._context.fillText(text, x, y, maxWidth); } } strokeRect(x: number, y: number, w: number, h: number): void { this._context.strokeRect(x, y, w, h); } strokeText(text: string, x: number, y: number, maxWidth?: number): void { if (arguments.length === 3) { this._context.strokeText(text, x, y); } else { this._context.strokeText(text, x, y, maxWidth); } } measureText(text: string): TextMetrics { return this._context.measureText(text); } createLinearGradient( x0: number, y0: number, x1: number, y1: number ): CanvasGradient { return this._context.createLinearGradient(x0, y0, x1, y1); } createRadialGradient( x0: number, y0: number, r0: number, x1: number, y1: number, r1: number ): CanvasGradient { return this._context.createRadialGradient(x0, y0, r0, x1, y1, r1); } createPattern( image: HTMLImageElement | HTMLCanvasElement | HTMLVideoElement, repetition: string ): CanvasPattern | null { return this._context.createPattern(image, repetition); } createImageData(imageData: ImageData): ImageData; createImageData(sw: number, sh: number): ImageData; createImageData() { // eslint-disable-next-line prefer-spread, prefer-rest-params return this._context.createImageData.apply(this._context, arguments); } getImageData(sx: number, sy: number, sw: number, sh: number): ImageData { return this._context.getImageData(sx, sy, sw, sh); } putImageData(imagedata: ImageData, dx: number, dy: number): void; putImageData( imagedata: ImageData, dx: number, dy: number, dirtyX: number, dirtyY: number, dirtyWidth: number, dirtyHeight: number ): void; putImageData(): void { // eslint-disable-next-line prefer-spread, prefer-rest-params this._context.putImageData.apply(this._context, arguments); } drawImage( image: | HTMLImageElement | HTMLCanvasElement | HTMLVideoElement | ImageBitmap, dstX: number, dstY: number ): void; drawImage( image: | HTMLImageElement | HTMLCanvasElement | HTMLVideoElement | ImageBitmap, dstX: number, dstY: number, dstW: number, dstH: number ): void; drawImage( image: | HTMLImageElement | HTMLCanvasElement | HTMLVideoElement | ImageBitmap, srcX: number, srcY: number, srcW: number, srcH: number, dstX: number, dstY: number, dstW: number, dstH: number ): void; drawImage(): void { // eslint-disable-next-line prefer-spread, prefer-rest-params this._context.drawImage.apply(this._context, arguments); } drawFocusIfNeeded(element: Element): void { this._context.drawFocusIfNeeded(element); } private _disposed = false; private _state: Private.State; private _context: CanvasRenderingContext2D; } /** * The namespace for the module implementation details. */ namespace Private { /** * The index of next valid pool object. */ let pi = -1; /** * A state object allocation pool. */ const pool: State[] = []; /** * An object which holds the state for a gc. */ export class State { /** * Create a gc state object from a 2D canvas context. */ static create(context: CanvasRenderingContext2D): State { let state = pi < 0 ? new State() : pool[pi--]; state.next = null; state.fillStyle = context.fillStyle; state.font = context.font; state.globalAlpha = context.globalAlpha; state.globalCompositeOperation = context.globalCompositeOperation; state.imageSmoothingEnabled = context.imageSmoothingEnabled; state.lineCap = context.lineCap; state.lineDashOffset = context.lineDashOffset; state.lineJoin = context.lineJoin; state.lineWidth = context.lineWidth; state.miterLimit = context.miterLimit; state.shadowBlur = context.shadowBlur; state.shadowColor = context.shadowColor; state.shadowOffsetX = context.shadowOffsetX; state.shadowOffsetY = context.shadowOffsetY; state.strokeStyle = context.strokeStyle; state.textAlign = context.textAlign; state.textBaseline = context.textBaseline; return state; } /** * Clone an existing gc state object and add it to the state stack. */ static push(other: State): State { let state = pi < 0 ? new State() : pool[pi--]; state.next = other; state.fillStyle = other.fillStyle; state.font = other.font; state.globalAlpha = other.globalAlpha; state.globalCompositeOperation = other.globalCompositeOperation; state.imageSmoothingEnabled = other.imageSmoothingEnabled; state.lineCap = other.lineCap; state.lineDashOffset = other.lineDashOffset; state.lineJoin = other.lineJoin; state.lineWidth = other.lineWidth; state.miterLimit = other.miterLimit; state.shadowBlur = other.shadowBlur; state.shadowColor = other.shadowColor; state.shadowOffsetX = other.shadowOffsetX; state.shadowOffsetY = other.shadowOffsetY; state.strokeStyle = other.strokeStyle; state.textAlign = other.textAlign; state.textBaseline = other.textBaseline; return state; } /** * Pop the next state object and return the current to the pool */ static pop(state: State): State { state.fillStyle = ''; state.strokeStyle = ''; pool[++pi] = state; return state.next!; } next: State | null; fillStyle: string | CanvasGradient | CanvasPattern; font: string; globalAlpha: number; globalCompositeOperation: string; imageSmoothingEnabled: boolean; lineCap: string; lineDashOffset: number; lineJoin: string; lineWidth: number; miterLimit: number; shadowBlur: number; shadowColor: string; shadowOffsetX: number; shadowOffsetY: number; strokeStyle: string | CanvasGradient | CanvasPattern; textAlign: string; textBaseline: string; } } lumino-2021.12.13/packages/datagrid/src/hyperlinkrenderer.ts000066400000000000000000000236011415564225700236530ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2019, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { CellRenderer } from './cellrenderer'; import { GraphicsContext } from './graphicscontext'; import { TextRenderer } from './textrenderer'; /** * A cell renderer which renders data values as text. */ export class HyperlinkRenderer extends TextRenderer { /** * Construct a new text renderer. * * @param options - The options for initializing the renderer. */ constructor(options: HyperlinkRenderer.IOptions = {}) { // Set default parameters before passing over the super. options.textColor = options.textColor || 'navy'; options.font = options.font || 'bold 12px sans-serif'; super(options); this.url = options.url; this.urlName = options.urlName; } /** * The URL address. */ readonly url: CellRenderer.ConfigOption | undefined; /** * The friendly link name. */ readonly urlName: CellRenderer.ConfigOption | undefined; /** * Draw the text for the cell. * * @param gc - The graphics context to use for drawing. * * @param config - The configuration data for the cell. */ drawText(gc: GraphicsContext, config: CellRenderer.CellConfig): void { // Resolve the font for the cell. let font = CellRenderer.resolveOption(this.font, config); // Bail if there is no font to draw. if (!font) { return; } // Resolve for the friendly URL name. let urlName = CellRenderer.resolveOption(this.urlName, config); // Resolve the text color for the cell. let color = CellRenderer.resolveOption(this.textColor, config); // Bail if there is no text color to draw. if (!color) { return; } const format = this.format; let text; // If we have a friendly URL name, use that. if (urlName) { text = format({ ...config, value: urlName } as CellRenderer.CellConfig); } else { // Otherwise use the raw value attribute. text = format(config); } // Bail if there is no text to draw. if (!text) { return; } // Resolve the vertical and horizontal alignment. let vAlign = CellRenderer.resolveOption(this.verticalAlignment, config); let hAlign = CellRenderer.resolveOption(this.horizontalAlignment, config); // Resolve the elision direction let elideDirection = CellRenderer.resolveOption( this.elideDirection, config ); // Resolve the text wrapping flag let wrapText = CellRenderer.resolveOption(this.wrapText, config); // Compute the padded text box height for the specified alignment. let boxHeight = config.height - (vAlign === 'center' ? 1 : 2); // Bail if the text box has no effective size. if (boxHeight <= 0) { return; } // Compute the text height for the gc font. let textHeight = HyperlinkRenderer.measureFontHeight(font); // Set up the text position variables. let textX: number; let textY: number; let boxWidth: number; // Compute the Y position for the text. switch (vAlign) { case 'top': textY = config.y + 2 + textHeight; break; case 'center': textY = config.y + config.height / 2 + textHeight / 2; break; case 'bottom': textY = config.y + config.height - 2; break; default: throw 'unreachable'; } // Compute the X position for the text. switch (hAlign) { case 'left': textX = config.x + 8; boxWidth = config.width - 14; break; case 'center': textX = config.x + config.width / 2; boxWidth = config.width; break; case 'right': textX = config.x + config.width - 8; boxWidth = config.width - 14; break; default: throw 'unreachable'; } // Clip the cell if the text is taller than the text box height. if (textHeight > boxHeight) { gc.beginPath(); gc.rect(config.x, config.y, config.width, config.height - 1); gc.clip(); } // Set the gc state. gc.font = font; gc.fillStyle = color; gc.textAlign = hAlign; gc.textBaseline = 'bottom'; // The current text width in pixels. let textWidth = gc.measureText(text).width; // Apply text wrapping if enabled. if (wrapText && textWidth > boxWidth) { // Make sure box clipping happens. gc.beginPath(); gc.rect(config.x, config.y, config.width, config.height - 1); gc.clip(); // Split column name to words based on // whitespace preceding a word boundary. // "Hello world" --> ["Hello ", "world"] const wordsInColumn = text.split(/\s(?=\b)/); // Y-coordinate offset for any additional lines let curY = textY; let textInCurrentLine = wordsInColumn.shift()!; // Single word. Applying text wrap on word by splitting // it into characters and fitting the maximum number of // characters possible per line (box width). if (wordsInColumn.length === 0) { let curLineTextWidth = gc.measureText(textInCurrentLine).width; while (curLineTextWidth > boxWidth && textInCurrentLine !== '') { // Iterating from the end of the string until we find a // substring (0,i) which has a width less than the box width. for (let i = textInCurrentLine.length; i > 0; i--) { const curSubString = textInCurrentLine.substring(0, i); const curSubStringWidth = gc.measureText(curSubString).width; if (curSubStringWidth < boxWidth || curSubString.length === 1) { // Found a substring which has a width less than the current // box width. Rendering that substring on the current line // and setting the remainder of the parent string as the next // string to iterate on for the next line. const nextLineText = textInCurrentLine.substring( i, textInCurrentLine.length ); textInCurrentLine = nextLineText; curLineTextWidth = gc.measureText(textInCurrentLine).width; gc.fillText(curSubString, textX, curY); curY += textHeight; // No need to continue iterating after we identified // an index to break the string on. break; } } } } // Multiple words in column header. Fitting maximum // number of words possible per line (box width). else { while (wordsInColumn.length !== 0) { // Processing the next word in the queue. const curWord = wordsInColumn.shift(); // Joining that word with the existing text for // the current line. const incrementedText = [textInCurrentLine, curWord].join(' '); const incrementedTextWidth = gc.measureText(incrementedText).width; if (incrementedTextWidth > boxWidth) { // If the newly combined text has a width larger than // the box width, we render the line before the current // word was added. We set the current word as the next // line. gc.fillText(textInCurrentLine, textX, curY); curY += textHeight; textInCurrentLine = curWord!; } else { // The combined text hasd a width less than the box width. We // set the the current line text to be the new combined text. textInCurrentLine = incrementedText; } } } gc.fillText(textInCurrentLine!, textX, curY); // Terminating the call here as we don't want // to apply text eliding when wrapping is active. return; } // Elide text that is too long let elide = '\u2026'; // Compute elided text if (elideDirection === 'right') { while (textWidth > boxWidth && text.length > 1) { if (text.length > 4 && textWidth >= 2 * boxWidth) { // If text width is substantially bigger, take half the string text = text.substring(0, text.length / 2 + 1) + elide; } else { // Otherwise incrementally remove the last character text = text.substring(0, text.length - 2) + elide; } textWidth = gc.measureText(text).width; } } else { while (textWidth > boxWidth && text.length > 1) { if (text.length > 4 && textWidth >= 2 * boxWidth) { // If text width is substantially bigger, take half the string text = elide + text.substring(text.length / 2); } else { // Otherwise incrementally remove the last character text = elide + text.substring(2); } textWidth = gc.measureText(text).width; } } // Draw the text for the cell. gc.fillText(text, textX, textY); } } export namespace HyperlinkRenderer { /** * A type alias for the supported vertical alignment modes. */ export type VerticalAlignment = 'top' | 'center' | 'bottom'; /** * A type alias for the supported horizontal alignment modes. */ export type HorizontalAlignment = 'left' | 'center' | 'right'; /** * A type alias for the supported ellipsis sides. */ export type ElideDirection = 'left' | 'right'; /** * An options object for initializing a text renderer. */ export interface IOptions extends TextRenderer.IOptions { /** * The URL address */ url?: CellRenderer.ConfigOption | undefined; /** * The friendly link name. * * The default is the URL itself. */ urlName?: CellRenderer.ConfigOption | undefined; } } lumino-2021.12.13/packages/datagrid/src/index.ts000066400000000000000000000017241415564225700212300ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2019, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ export * from './basickeyhandler'; export * from './basicmousehandler'; export * from './basicselectionmodel'; export * from './cellrenderer'; export * from './celleditor'; export * from './celleditorcontroller'; export * from './datagrid'; export * from './datamodel'; export * from './graphicscontext'; export * from './jsonmodel'; export * from './renderermap'; export * from './selectionmodel'; export * from './sectionlist'; export * from './textrenderer'; export * from './hyperlinkrenderer'; export * from './cellgroup'; lumino-2021.12.13/packages/datagrid/src/jsonmodel.ts000066400000000000000000000206211415564225700221100ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2019, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { ReadonlyJSONObject } from '@lumino/coreutils'; import { DataModel } from './datamodel'; /** * A data model implementation for in-memory JSON data. */ export class JSONModel extends DataModel { /** * Create a data model with static JSON data. * * @param options - The options for initializing the data model. */ constructor(options: JSONModel.IOptions) { super(); let split = Private.splitFields(options.schema); this._data = options.data; this._bodyFields = split.bodyFields; this._headerFields = split.headerFields; this._missingValues = Private.createMissingMap(options.schema); } /** * Get the row count for a region in the data model. * * @param region - The row region of interest. * * @returns - The row count for the region. */ rowCount(region: DataModel.RowRegion): number { if (region === 'body') { return this._data.length; } return 1; // TODO multiple column-header rows? } /** * Get the column count for a region in the data model. * * @param region - The column region of interest. * * @returns - The column count for the region. */ columnCount(region: DataModel.ColumnRegion): number { if (region === 'body') { return this._bodyFields.length; } return this._headerFields.length; } /** * Get the data value for a cell in the data model. * * @param region - The cell region of interest. * * @param row - The row index of the cell of interest. * * @param column - The column index of the cell of interest. * * @returns - The data value for the specified cell. * * #### Notes * A `missingValue` as defined by the schema is converted to `null`. */ data(region: DataModel.CellRegion, row: number, column: number): any { // Set up the field and value variables. let field: JSONModel.Field; let value: any; // Look up the field and value for the region. switch (region) { case 'body': field = this._bodyFields[column]; value = this._data[row][field.name]; break; case 'column-header': field = this._bodyFields[column]; value = field.title || field.name; break; case 'row-header': field = this._headerFields[column]; value = this._data[row][field.name]; break; case 'corner-header': field = this._headerFields[column]; value = field.title || field.name; break; default: throw 'unreachable'; } // Test whether the value is a missing value. let missing = this._missingValues !== null && typeof value === 'string' && this._missingValues[value] === true; // Return the final value. return missing ? null : value; } /** * Get the metadata for a cell in the data model. * * @param region - The cell region of interest. * * @param row - The row index of the cell of of interest. * * @param column - The column index of the cell of interest. * * @returns The metadata for the cell. */ metadata( region: DataModel.CellRegion, row: number, column: number ): DataModel.Metadata { if (region === 'body' || region === 'column-header') { return this._bodyFields[column]; } return this._headerFields[column]; } private _data: JSONModel.DataSource; private _bodyFields: JSONModel.Field[]; private _headerFields: JSONModel.Field[]; private _missingValues: Private.MissingValuesMap | null; } /** * The namespace for the `JSONModel` class statics. */ export namespace JSONModel { /** * An object which describes a column of data in the model. * * #### Notes * This is based on the JSON Table Schema specification: * https://specs.frictionlessdata.io/table-schema/ */ export type Field = { /** * The name of the column. * * This is used as the key to extract a value from a data record. * It is also used as the column header label, unless the `title` * property is provided. */ readonly name: string; /** * The type of data held in the column. */ readonly type?: string; /** * The format of the data in the column. */ readonly format?: string; /** * The human readable name for the column. * * This is used as the label for the column header. */ readonly title?: string; // TODO want/need support for any these? // description?: string; // constraints?: IConstraints; // rdfType?: string; }; /** * An object when specifies the schema for a data model. * * #### Notes * This is based on the JSON Table Schema specification: * https://specs.frictionlessdata.io/table-schema/ */ export type Schema = { /** * The fields which describe the data model columns. * * Primary key fields are rendered as row header columns. */ readonly fields: Field[]; /** * The values to treat as "missing" data. * * Missing values are automatically converted to `null`. */ readonly missingValues?: string[]; /** * The field names which act as primary keys. * * Primary key fields are rendered as row header columns. */ readonly primaryKey?: string | string[]; // TODO want/need support for this? // foreignKeys?: IForeignKey[]; }; /** * A type alias for a data source for a JSON data model. * * A data source is an array of JSON object records which represent * the rows of the table. The keys of the records correspond to the * field names of the columns. */ export type DataSource = ReadonlyArray; /** * An options object for initializing a JSON data model. */ export interface IOptions { /** * The schema for the for the data model. * * The schema should be treated as an immutable object. */ schema: Schema; /** * The data source for the data model. * * The data model takes full ownership of the data source. */ data: DataSource; } } /** * The namespace for the module implementation details. */ namespace Private { /** * An object which holds the results of splitting schema fields. */ export type SplitFieldsResult = { /** * The non-primary key fields to use for the grid body. */ bodyFields: JSONModel.Field[]; /** * The primary key fields to use for the grid headers. */ headerFields: JSONModel.Field[]; }; /** * Split the schema fields into header and body fields. */ export function splitFields(schema: JSONModel.Schema): SplitFieldsResult { // Normalize the primary keys. let primaryKeys: string[]; if (schema.primaryKey === undefined) { primaryKeys = []; } else if (typeof schema.primaryKey === 'string') { primaryKeys = [schema.primaryKey]; } else { primaryKeys = schema.primaryKey; } // Separate the fields for the body and header. let bodyFields: JSONModel.Field[] = []; let headerFields: JSONModel.Field[] = []; for (let field of schema.fields) { if (primaryKeys.indexOf(field.name) === -1) { bodyFields.push(field); } else { headerFields.push(field); } } // Return the separated fields. return { bodyFields, headerFields }; } /** * A type alias for a missing value map. */ export type MissingValuesMap = { [key: string]: boolean }; /** * Create a missing values map for a schema. * * This returns `null` if there are no missing values. */ export function createMissingMap( schema: JSONModel.Schema ): MissingValuesMap | null { // Bail early if there are no missing values. if (!schema.missingValues || schema.missingValues.length === 0) { return null; } // Collect the missing values into a map. let result: MissingValuesMap = Object.create(null); for (let value of schema.missingValues) { result[value] = true; } // Return the populated map. return result; } } lumino-2021.12.13/packages/datagrid/src/notification.ts000066400000000000000000000140651415564225700226110ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2019, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { Message } from '@lumino/messaging'; import { Widget } from '@lumino/widgets'; /** * A widget which implements a notification popup. */ export class Notification extends Widget { /** * Construct a new notification. * * @param options - The options for initializing the notification. */ constructor(options: Notification.IOptions) { super({ node: Private.createNode() }); this.addClass('lm-DataGrid-notification'); this.setFlag(Widget.Flag.DisallowLayout); this._target = options.target; this._message = options.message || ''; this._placement = options.placement || 'bottom'; Widget.attach(this, document.body); if (options.timeout && options.timeout > 0) { setTimeout(() => { this.close(); }, options.timeout); } } /** * Handle the DOM events for the notification. * * @param event - The DOM event sent to the notification. * * #### Notes * This method implements the DOM `EventListener` interface and is * called in response to events on the notification's DOM node. * * This should not be called directly by user code. */ handleEvent(event: Event): void { switch (event.type) { case 'mousedown': this._evtMouseDown(event as MouseEvent); break; case 'contextmenu': event.preventDefault(); event.stopPropagation(); break; } } /** * Get the placement of the notification. */ get placement(): Notification.Placement { return this._placement; } /** * Set the placement of the notification. */ set placement(value: Notification.Placement) { // Do nothing if the placement does not change. if (this._placement === value) { return; } // Update the internal placement. this._placement = value; // Schedule an update for notification. this.update(); } /** * Get the current value of the message. */ get message(): string { return this._message; } /** * Set the current value of the message. * */ set message(value: string) { // Do nothing if the value does not change. if (this._message === value) { return; } // Update the internal value. this._message = value; // Schedule an update for notification. this.update(); } /** * Get the node presenting the message. */ get messageNode(): HTMLSpanElement { return this.node.getElementsByClassName( 'lm-DataGrid-notificationMessage' )[0] as HTMLSpanElement; } /** * A method invoked on a 'before-attach' message. */ protected onBeforeAttach(msg: Message): void { this.node.addEventListener('mousedown', this); this.update(); } /** * A method invoked on an 'after-detach' message. */ protected onAfterDetach(msg: Message): void { this.node.removeEventListener('mousedown', this); } /** * A method invoked on an 'update-request' message. */ protected onUpdateRequest(msg: Message): void { const targetRect = this._target.getBoundingClientRect(); const style = this.node.style; switch (this._placement) { case 'bottom': style.left = targetRect.left + 'px'; style.top = targetRect.bottom + 'px'; break; case 'top': style.left = targetRect.left + 'px'; style.height = targetRect.top + 'px'; style.top = '0'; style.alignItems = 'flex-end'; style.justifyContent = 'flex-end'; break; case 'left': style.left = '0'; style.width = targetRect.left + 'px'; style.top = targetRect.top + 'px'; style.alignItems = 'flex-end'; style.justifyContent = 'flex-end'; break; case 'right': style.left = targetRect.right + 'px'; style.top = targetRect.top + 'px'; break; } this.messageNode.innerHTML = this._message; } /** * Handle the `'mousedown'` event for the notification. */ private _evtMouseDown(event: MouseEvent): void { // Do nothing if it's not a left mouse press. if (event.button !== 0) { return; } event.preventDefault(); event.stopPropagation(); this.close(); } private _target: HTMLElement; private _message: string = ''; private _placement: Notification.Placement; } /** * The namespace for the `Notification` class statics. */ export namespace Notification { /** * A type alias for a notification placement. */ export type Placement = 'top' | 'bottom' | 'left' | 'right'; /** * An options object for creating a notification. */ export interface IOptions { /** * Target element to attach notification to. * */ target: HTMLElement; /** * The message to show on notification. */ message?: string; /** * The placement of the notification. * * The default is `'bottom'`. */ placement?: Placement; /** * Duration in ms after which to close notification popup. * * The default is undefined, and notification is kept visible * Timeout value needs to be greater than zero */ timeout?: number; } } /** * The namespace for the module implementation details. */ namespace Private { /** * Create the DOM node for notification. */ export function createNode(): HTMLElement { const node = document.createElement('div'); const container = document.createElement('div'); container.className = 'lm-DataGrid-notificationContainer'; const message = document.createElement('span'); message.className = 'lm-DataGrid-notificationMessage'; container.appendChild(message); node.appendChild(container); return node; } } lumino-2021.12.13/packages/datagrid/src/renderermap.ts000066400000000000000000000054421415564225700224260ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2019, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { ISignal, Signal } from '@lumino/signaling'; import { CellRenderer } from './cellrenderer'; import { DataModel } from './datamodel'; import { TextRenderer } from './textrenderer'; /** * A class which manages the mapping of cell renderers. */ export class RendererMap { /** * Construct a new renderer map. * * @param values - The initial values for the map. * * @param fallback - The renderer of last resort. */ constructor(values: RendererMap.Values = {}, fallback?: CellRenderer) { this._values = { ...values }; this._fallback = fallback || new TextRenderer(); } /** * A signal emitted when the renderer map has changed. */ get changed(): ISignal { return this._changed; } /** * Get the cell renderer to use for the given cell config. * * @param config - The cell config of interest. * * @returns The renderer to use for the cell. */ get(config: CellRenderer.CellConfig): CellRenderer { // Fetch the renderer from the values map. let renderer = this._values[config.region]; // Execute a resolver function if necessary. if (typeof renderer === 'function') { try { renderer = renderer(config); } catch (err) { renderer = undefined; console.error(err); } } // Return the renderer or the fallback. return renderer || this._fallback; } /** * Update the renderer map with new values * * @param values - The updated values for the map. * * @param fallback - The renderer of last resort. * * #### Notes * This method always emits the `changed` signal. */ update(values: RendererMap.Values = {}, fallback?: CellRenderer): void { this._values = { ...this._values, ...values }; this._fallback = fallback || this._fallback; this._changed.emit(undefined); } private _fallback: CellRenderer; private _values: RendererMap.Values; private _changed = new Signal(this); } /** * The namespace for the `RendererMap` class statics. */ export namespace RendererMap { /** * A type alias for a cell renderer resolver function. */ export type Resolver = CellRenderer.ConfigFunc; /** * A type alias for a `RendererMap` values type. */ export type Values = { [R in DataModel.CellRegion]?: Resolver | CellRenderer | undefined; }; } lumino-2021.12.13/packages/datagrid/src/sectionlist.ts000066400000000000000000000451331415564225700224630ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2019, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { ArrayExt } from '@lumino/algorithm'; /** * An object which manages a collection of variable sized sections. * * #### Notes * This class is an implementation detail. It is designed to manage * the variable row and column sizes for a data grid. User code will * not interact with this class directly. */ export class SectionList { /** * Construct a new section list. * * @param options - The options for initializing the list. */ constructor(options: SectionList.IOptions) { this._minimumSize = options.minimumSize || 2; this._defaultSize = Math.max( this._minimumSize, Math.floor(options.defaultSize) ); } /** * The total size of all sections in the list. * * #### Complexity * Constant. */ get length(): number { return this._length; } /** * The total number of sections in the list. * * #### Complexity * Constant. */ get count(): number { return this._count; } /** * Get the minimum size of sections in the list. * * #### Complexity * Constant. */ get minimumSize(): number { return this._minimumSize; } /** * Set the minimum size of sections in the list. * * #### Complexity * Linear on the number of resized sections. */ set minimumSize(value: number) { // Normalize the value. value = Math.max(2, Math.floor(value)); // Bail early if the value does not change. if (this._minimumSize === value) { return; } // Update the internal minimum size. this._minimumSize = value; // Update default size if larger than minimum size if (value > this._defaultSize) { this.defaultSize = value; } } /** * Get the default size of sections in the list. * * #### Complexity * Constant. */ get defaultSize(): number { return this._defaultSize; } /** * Set the default size of sections in the list. * * #### Complexity * Linear on the number of resized sections. */ set defaultSize(value: number) { // Normalize the value. value = Math.max(this._minimumSize, Math.floor(value)); // Bail early if the value does not change. if (this._defaultSize === value) { return; } // Compute the delta default size. let delta = value - this._defaultSize; // Update the internal default size. this._defaultSize = value; // Update the length. this._length += delta * (this._count - this._sections.length); // Bail early if there are no modified sections. if (this._sections.length === 0) { return; } // Recompute the offsets of the modified sections. for (let i = 0, n = this._sections.length; i < n; ++i) { // Look up the previous and current modified sections. let prev = this._sections[i - 1]; let curr = this._sections[i]; // Adjust the offset for the current section. if (prev) { let count = curr.index - prev.index - 1; curr.offset = prev.offset + prev.size + count * value; } else { curr.offset = curr.index * value; } } } /** * Clamp a size to the minimum section size * * @param size - The size to clamp. * * @returns The size or the section minimum size, whichever is larger */ clampSize(size: number): number { return Math.max(this._minimumSize, Math.floor(size)); } /** * Find the index of the section which covers the given offset. * * @param offset - The offset of the section of interest. * * @returns The index of the section which covers the given offset, * or `-1` if the offset is out of range. * * #### Complexity * Logarithmic on the number of resized sections. */ indexOf(offset: number): number { // Bail early if the offset is out of range. if (offset < 0 || offset >= this._length || this._count === 0) { return -1; } // Handle the simple case of no modified sections. if (this._sections.length === 0) { return Math.floor(offset / this._defaultSize); } // Find the modified section for the given offset. let i = ArrayExt.lowerBound(this._sections, offset, Private.offsetCmp); // Return the index of an exact match. if (i < this._sections.length && this._sections[i].offset <= offset) { return this._sections[i].index; } // Handle the case of no modified sections before the offset. if (i === 0) { return Math.floor(offset / this._defaultSize); } // Compute the index from the previous modified section. let section = this._sections[i - 1]; let span = offset - (section.offset + section.size); return section.index + Math.floor(span / this._defaultSize) + 1; } /** * Find the offset of the section at the given index. * * @param index - The index of the section of interest. * * @returns The offset of the section at the given index, or `-1` * if the index is out of range. * * #### Undefined Behavior * An `index` which is non-integral. * * #### Complexity * Logarithmic on the number of resized sections. */ offsetOf(index: number): number { // Bail early if the index is out of range. if (index < 0 || index >= this._count) { return -1; } // Handle the simple case of no modified sections. if (this._sections.length === 0) { return index * this._defaultSize; } // Find the modified section for the given index. let i = ArrayExt.lowerBound(this._sections, index, Private.indexCmp); // Return the offset of an exact match. if (i < this._sections.length && this._sections[i].index === index) { return this._sections[i].offset; } // Handle the case of no modified sections before the index. if (i === 0) { return index * this._defaultSize; } // Compute the offset from the previous modified section. let section = this._sections[i - 1]; let span = index - section.index - 1; return section.offset + section.size + span * this._defaultSize; } /** * Find the extent of the section at the given index. * * @param index - The index of the section of interest. * * @returns The extent of the section at the given index, or `-1` * if the index is out of range. * * #### Undefined Behavior * An `index` which is non-integral. * * #### Complexity * Logarithmic on the number of resized sections. */ extentOf(index: number): number { // Bail early if the index is out of range. if (index < 0 || index >= this._count) { return -1; } // Handle the simple case of no modified sections. if (this._sections.length === 0) { return (index + 1) * this._defaultSize - 1; } // Find the modified section for the given index. let i = ArrayExt.lowerBound(this._sections, index, Private.indexCmp); // Return the offset of an exact match. if (i < this._sections.length && this._sections[i].index === index) { return this._sections[i].offset + this._sections[i].size - 1; } // Handle the case of no modified sections before the index. if (i === 0) { return (index + 1) * this._defaultSize - 1; } // Compute the offset from the previous modified section. let section = this._sections[i - 1]; let span = index - section.index; return section.offset + section.size + span * this._defaultSize - 1; } /** * Find the size of the section at the given index. * * @param index - The index of the section of interest. * * @returns The size of the section at the given index, or `-1` * if the index is out of range. * * #### Undefined Behavior * An `index` which is non-integral. * * #### Complexity * Logarithmic on the number of resized sections. */ sizeOf(index: number): number { // Bail early if the index is out of range. if (index < 0 || index >= this._count) { return -1; } // Handle the simple case of no modified sections. if (this._sections.length === 0) { return this._defaultSize; } // Find the modified section for the given index. let i = ArrayExt.lowerBound(this._sections, index, Private.indexCmp); // Return the size of an exact match. if (i < this._sections.length && this._sections[i].index === index) { return this._sections[i].size; } // Return the default size for all other cases. return this._defaultSize; } /** * Resize a section in the list. * * @param index - The index of the section to resize. This method * is a no-op if this value is out of range. * * @param size - The new size of the section. This value will be * clamped to an integer `>= 0`. * * #### Undefined Behavior * An `index` which is non-integral. * * #### Complexity * Linear on the number of resized sections. */ resize(index: number, size: number): void { // Bail early if the index is out of range. if (index < 0 || index >= this._count) { return; } // Clamp the size to an integer >= minimum size. size = Math.max(this._minimumSize, Math.floor(size)); // Find the modified section for the given index. let i = ArrayExt.lowerBound(this._sections, index, Private.indexCmp); // Update or create the modified section as needed. let delta: number; if (i < this._sections.length && this._sections[i].index === index) { let section = this._sections[i]; delta = size - section.size; section.size = size; } else if (i === 0) { let offset = index * this._defaultSize; ArrayExt.insert(this._sections, i, { index, offset, size }); delta = size - this._defaultSize; } else { let section = this._sections[i - 1]; let span = index - section.index - 1; let offset = section.offset + section.size + span * this._defaultSize; ArrayExt.insert(this._sections, i, { index, offset, size }); delta = size - this._defaultSize; } // Adjust the length. this._length += delta; // Update all modified sections after the resized section. for (let j = i + 1, n = this._sections.length; j < n; ++j) { this._sections[j].offset += delta; } } /** * Insert sections into the list. * * @param index - The index at which to insert the sections. This * value will be clamped to the bounds of the list. * * @param count - The number of sections to insert. This method * is a no-op if this value is `<= 0`. * * #### Undefined Behavior * An `index` or `count` which is non-integral. * * #### Complexity * Linear on the number of resized sections. */ insert(index: number, count: number): void { // Bail early if there are no sections to insert. if (count <= 0) { return; } // Clamp the index to the bounds of the list. index = Math.max(0, Math.min(index, this._count)); // Add the new sections to the totals. let span = count * this._defaultSize; this._count += count; this._length += span; // Bail early if there are no modified sections to update. if (this._sections.length === 0) { return; } // Find the modified section for the given index. let i = ArrayExt.lowerBound(this._sections, index, Private.indexCmp); // Update all modified sections after the insert location. for (let n = this._sections.length; i < n; ++i) { let section = this._sections[i]; section.index += count; section.offset += span; } } /** * Remove sections from the list. * * @param index - The index of the first section to remove. This * method is a no-op if this value is out of range. * * @param count - The number of sections to remove. This method * is a no-op if this value is `<= 0`. * * #### Undefined Behavior * An `index` or `count` which is non-integral. * * #### Complexity * Linear on the number of resized sections. */ remove(index: number, count: number): void { // Bail early if there is nothing to remove. if (index < 0 || index >= this._count || count <= 0) { return; } // Clamp the count to the bounds of the list. count = Math.min(this._count - index, count); // Handle the simple case of no modified sections to update. if (this._sections.length === 0) { this._count -= count; this._length -= count * this._defaultSize; return; } // Handle the simple case of removing all sections. if (count === this._count) { this._length = 0; this._count = 0; this._sections.length = 0; return; } // Find the modified section for the start index. let i = ArrayExt.lowerBound(this._sections, index, Private.indexCmp); // Find the modified section for the end index. let j = ArrayExt.lowerBound( this._sections, index + count, Private.indexCmp ); // Remove the relevant modified sections. let removed = this._sections.splice(i, j - i); // Compute the total removed span. let span = (count - removed.length) * this._defaultSize; for (let k = 0, n = removed.length; k < n; ++k) { span += removed[k].size; } // Adjust the totals. this._count -= count; this._length -= span; // Update all modified sections after the removed span. for (let k = i, n = this._sections.length; k < n; ++k) { let section = this._sections[k]; section.index -= count; section.offset -= span; } } /** * Move sections within the list. * * @param index - The index of the first section to move. This method * is a no-op if this value is out of range. * * @param count - The number of sections to move. This method is a * no-op if this value is `<= 0`. * * @param destination - The destination index for the first section. * This value will be clamped to the allowable range. * * #### Undefined Behavior * An `index`, `count`, or `destination` which is non-integral. * * #### Complexity * Linear on the number of moved resized sections. */ move(index: number, count: number, destination: number): void { // Bail early if there is nothing to move. if (index < 0 || index >= this._count || count <= 0) { return; } // Handle the simple case of no modified sections. if (this._sections.length === 0) { return; } // Clamp the move count to the limit. count = Math.min(count, this._count - index); // Clamp the destination index to the limit. destination = Math.min(Math.max(0, destination), this._count - count); // Bail early if there is no effective move. if (index === destination) { return; } // Compute the first affected index. let i1 = Math.min(index, destination); // Look up the first affected modified section. let k1 = ArrayExt.lowerBound(this._sections, i1, Private.indexCmp); // Bail early if there are no affected modified sections. if (k1 === this._sections.length) { return; } // Compute the last affected index. let i2 = Math.max(index + count - 1, destination + count - 1); // Look up the last affected modified section. let k2 = ArrayExt.upperBound(this._sections, i2, Private.indexCmp) - 1; // Bail early if there are no affected modified sections. if (k2 < k1) { return; } // Compute the pivot index. let pivot = destination < index ? index : index + count; // Compute the count for each side of the pivot. let count1 = pivot - i1; let count2 = i2 - pivot + 1; // Compute the span for each side of the pivot. let span1 = count1 * this._defaultSize; let span2 = count2 * this._defaultSize; // Adjust the spans for the modified sections. for (let j = k1; j <= k2; ++j) { let section = this._sections[j]; if (section.index < pivot) { span1 += section.size - this._defaultSize; } else { span2 += section.size - this._defaultSize; } } // Look up the pivot section. let k3 = ArrayExt.lowerBound(this._sections, pivot, Private.indexCmp); // Rotate the modified sections if needed. if (k1 <= k3 && k3 <= k2) { ArrayExt.rotate(this._sections, k3 - k1, k1, k2); } // Adjust the modified section indices and offsets. for (let j = k1; j <= k2; ++j) { let section = this._sections[j]; if (section.index < pivot) { section.index += count2; section.offset += span2; } else { section.index -= count1; section.offset -= span1; } } } /** * Reset all modified sections to the default size. * * #### Complexity * Constant. */ reset(): void { this._sections.length = 0; this._length = this._count * this._defaultSize; } /** * Remove all sections from the list. * * #### Complexity * Constant. */ clear(): void { this._count = 0; this._length = 0; this._sections.length = 0; } private _count = 0; private _length = 0; private _minimumSize: number; private _defaultSize: number; private _sections: Private.Section[] = []; } /** * The namespace for the `SectionList` class statics. */ export namespace SectionList { /** * An options object for initializing a section list. */ export interface IOptions { /** * The size of new sections added to the list. */ defaultSize: number; /** * The minimum size of the section list. */ minimumSize?: number; } } /** * The namespace for the module implementation details. */ namespace Private { /** * An object which represents a modified section. */ export type Section = { /** * The index of the section. * * This is always `>= 0`. */ index: number; /** * The offset of the section. */ offset: number; /** * The size of the section. * * This is always `>= 0`. */ size: number; }; /** * A comparison function for searching by offset. */ export function offsetCmp(section: Section, offset: number): number { if (offset < section.offset) { return 1; } if (section.offset + section.size <= offset) { return -1; } return 0; } /** * A comparison function for searching by index. */ export function indexCmp(section: Section, index: number): number { return section.index - index; } } lumino-2021.12.13/packages/datagrid/src/selectionmodel.ts000066400000000000000000000212271415564225700231270ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2019, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { IIterator, some } from '@lumino/algorithm'; import { ISignal, Signal } from '@lumino/signaling'; import { DataModel } from './datamodel'; /** * A base class for creating data grid selection models. * * #### Notes * If the predefined selection models are insufficient for a particular * use case, a custom model can be defined which derives from this class. */ export abstract class SelectionModel { /** * Construct a new selection model. * * @param options - The options for initializing the model. */ constructor(options: SelectionModel.IOptions) { this.dataModel = options.dataModel; this._selectionMode = options.selectionMode || 'cell'; this.dataModel.changed.connect(this.onDataModelChanged, this); } /** * Whether the selection model is empty. * * #### Notes * An empty selection model will yield an empty `selections` iterator. */ abstract readonly isEmpty: boolean; /** * The row index of the cursor. * * This is `-1` if the selection model is empty. */ abstract readonly cursorRow: number; /** * The column index of the cursor. * * This is `-1` if the selection model is empty. */ abstract readonly cursorColumn: number; /** * Move cursor down/up/left/right while making sure it remains * within the bounds of selected rectangles * * @param direction - The direction of the movement. */ abstract moveCursorWithinSelections( direction: SelectionModel.CursorMoveDirection ): void; /** * Get the current selection in the selection model. * * @returns The current selection or `null`. * * #### Notes * This is the selection which holds the cursor. */ abstract currentSelection(): SelectionModel.Selection | null; /** * Get an iterator of the selections in the model. * * @returns A new iterator of the selections in the model. * * #### Notes * The data grid will render the selections in order. */ abstract selections(): IIterator; /** * Select the specified cells. * * @param args - The arguments for the selection. */ abstract select(args: SelectionModel.SelectArgs): void; /** * Clear all selections in the selection model. */ abstract clear(): void; /** * A signal emitted when the selection model has changed. */ get changed(): ISignal { return this._changed; } /** * The data model associated with the selection model. */ readonly dataModel: DataModel; /** * Get the selection mode for the model. */ get selectionMode(): SelectionModel.SelectionMode { return this._selectionMode; } /** * Set the selection mode for the model. * * #### Notes * This will clear the selection model. */ set selectionMode(value: SelectionModel.SelectionMode) { // Bail early if the mode does not change. if (this._selectionMode === value) { return; } // Update the internal mode. this._selectionMode = value; // Clear the current selections. this.clear(); } /** * Test whether any selection intersects a row. * * @param index - The row index of interest. * * @returns Whether any selection intersects the row. * * #### Notes * This method may be reimplemented in a subclass. */ isRowSelected(index: number): boolean { return some(this.selections(), s => Private.containsRow(s, index)); } /** * Test whether any selection intersects a column. * * @param index - The column index of interest. * * @returns Whether any selection intersects the column. * * #### Notes * This method may be reimplemented in a subclass. */ isColumnSelected(index: number): boolean { return some(this.selections(), s => Private.containsColumn(s, index)); } /** * Test whether any selection intersects a cell. * * @param row - The row index of interest. * * @param column - The column index of interest. * * @returns Whether any selection intersects the cell. * * #### Notes * This method may be reimplemented in a subclass. */ isCellSelected(row: number, column: number): boolean { return some(this.selections(), s => Private.containsCell(s, row, column)); } /** * A signal handler for the data model `changed` signal. * * @param args - The arguments for the signal. * * #### Notes * Selection model implementations should update their selections * in a manner that is relevant for the changes to the data model. * * The default implementation of this method is a no-op. */ protected onDataModelChanged( sender: DataModel, args: DataModel.ChangedArgs ): void { // pass } /** * Emit the `changed` signal for the selection model. * * #### Notes * Subclasses should call this method whenever the selection model * has changed so that attached data grids can update themselves. */ protected emitChanged(): void { this._changed.emit(undefined); } private _changed = new Signal(this); private _selectionMode: SelectionModel.SelectionMode = 'cell'; } /** * The namespace for the `SelectionModel` class statics. */ export namespace SelectionModel { /** * A type alias for the selection mode. */ export type SelectionMode = 'row' | 'column' | 'cell'; /** * A type alias for the cursor move direction. */ export type CursorMoveDirection = 'up' | 'down' | 'left' | 'right' | 'none'; /** * A type alias for the clear mode. */ export type ClearMode = 'all' | 'current' | 'none'; /** * A type alias for the select args. */ export type SelectArgs = { /** * The first row of the selection. * * This may be greater than `r2`. */ r1: number; /** * The first column of the selection. * * This may be greater than `c2`. */ c1: number; /** * The last row of the selection. * * This may be less than `r1`. */ r2: number; /** * The last column of the selection. * * This may be less than `c1`. */ c2: number; /** * The row index for the cursor. * * This should be contained within the selection. */ cursorRow: number; /** * The column index for the cursor. * * This should be contained within the selection. */ cursorColumn: number; /** * Which of the existing selections to clear. */ clear: ClearMode; }; /** * A type alias for a selection in a selection model. */ export type Selection = { /** * The first row of the selection. * * This may be greater than `r2`. */ readonly r1: number; /** * The first column of the selection. * * This may be greater than `c2`. */ readonly c1: number; /** * The last row of the selection. * * This may be less than `r1`. */ readonly r2: number; /** * The last column of the selection. * * This may be less than `c1`. */ readonly c2: number; }; /** * An options object for initializing a selection model. */ export interface IOptions { /** * The data model for the selection model. */ dataModel: DataModel; /** * The selection mode for the model. * * The default is `'cell'`. */ selectionMode?: SelectionMode; } } /** * The namespace for the module implementation details. */ namespace Private { /** * Test whether a selection contains a given row. */ export function containsRow( selection: SelectionModel.Selection, row: number ): boolean { let { r1, r2 } = selection; return (row >= r1 && row <= r2) || (row >= r2 && row <= r1); } /** * Test whether a selection contains a given column. */ export function containsColumn( selection: SelectionModel.Selection, column: number ): boolean { let { c1, c2 } = selection; return (column >= c1 && column <= c2) || (column >= c2 && column <= c1); } /** * Test whether a selection contains a given cell. */ export function containsCell( selection: SelectionModel.Selection, row: number, column: number ): boolean { return containsRow(selection, row) && containsColumn(selection, column); } } lumino-2021.12.13/packages/datagrid/src/textrenderer.ts000066400000000000000000000662131415564225700226400ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2019, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { CellRenderer } from './cellrenderer'; import { GraphicsContext } from './graphicscontext'; /** * A cell renderer which renders data values as text. */ export class TextRenderer extends CellRenderer { /** * Construct a new text renderer. * * @param options - The options for initializing the renderer. */ constructor(options: TextRenderer.IOptions = {}) { super(); this.font = options.font || '12px sans-serif'; this.textColor = options.textColor || '#000000'; this.backgroundColor = options.backgroundColor || ''; this.verticalAlignment = options.verticalAlignment || 'center'; this.horizontalAlignment = options.horizontalAlignment || 'left'; this.format = options.format || TextRenderer.formatGeneric(); this.elideDirection = options.elideDirection || 'right'; this.wrapText = options.wrapText || false; } /** * The CSS shorthand font for drawing the text. */ readonly font: CellRenderer.ConfigOption; /** * The CSS color for drawing the text. */ readonly textColor: CellRenderer.ConfigOption; /** * The CSS color for the cell background. */ readonly backgroundColor: CellRenderer.ConfigOption; /** * The vertical alignment for the cell text. */ readonly verticalAlignment: CellRenderer.ConfigOption< TextRenderer.VerticalAlignment >; /** * The horizontal alignment for the cell text. */ readonly horizontalAlignment: CellRenderer.ConfigOption< TextRenderer.HorizontalAlignment >; /** * The format function for the cell value. */ readonly format: TextRenderer.FormatFunc; /** * Which side to draw the ellipsis. */ readonly elideDirection: CellRenderer.ConfigOption< TextRenderer.ElideDirection >; /** * Boolean flag for applying text wrapping. */ readonly wrapText: CellRenderer.ConfigOption; /** * Paint the content for a cell. * * @param gc - The graphics context to use for drawing. * * @param config - The configuration data for the cell. */ paint(gc: GraphicsContext, config: CellRenderer.CellConfig): void { this.drawBackground(gc, config); this.drawText(gc, config); } /** * Draw the background for the cell. * * @param gc - The graphics context to use for drawing. * * @param config - The configuration data for the cell. */ drawBackground(gc: GraphicsContext, config: CellRenderer.CellConfig): void { // Resolve the background color for the cell. let color = CellRenderer.resolveOption(this.backgroundColor, config); // Bail if there is no background color to draw. if (!color) { return; } // Fill the cell with the background color. gc.fillStyle = color; gc.fillRect(config.x, config.y, config.width, config.height); } /** * Draw the text for the cell. * * @param gc - The graphics context to use for drawing. * * @param config - The configuration data for the cell. */ drawText(gc: GraphicsContext, config: CellRenderer.CellConfig): void { // Resolve the font for the cell. let font = CellRenderer.resolveOption(this.font, config); // Bail if there is no font to draw. if (!font) { return; } // Resolve the text color for the cell. let color = CellRenderer.resolveOption(this.textColor, config); // Bail if there is no text color to draw. if (!color) { return; } // Format the cell value to text. let format = this.format; let text = format(config); // Bail if there is no text to draw. if (!text) { return; } // Resolve the vertical and horizontal alignment. let vAlign = CellRenderer.resolveOption(this.verticalAlignment, config); let hAlign = CellRenderer.resolveOption(this.horizontalAlignment, config); // Resolve the elision direction let elideDirection = CellRenderer.resolveOption( this.elideDirection, config ); // Resolve the text wrapping flag let wrapText = CellRenderer.resolveOption(this.wrapText, config); // Compute the padded text box height for the specified alignment. let boxHeight = config.height - (vAlign === 'center' ? 1 : 2); // Bail if the text box has no effective size. if (boxHeight <= 0) { return; } // Compute the text height for the gc font. let textHeight = TextRenderer.measureFontHeight(font); // Set up the text position variables. let textX: number; let textY: number; let boxWidth: number; // Compute the Y position for the text. switch (vAlign) { case 'top': textY = config.y + 2 + textHeight; break; case 'center': textY = config.y + config.height / 2 + textHeight / 2; break; case 'bottom': textY = config.y + config.height - 2; break; default: throw 'unreachable'; } // Compute the X position for the text. switch (hAlign) { case 'left': textX = config.x + 8; boxWidth = config.width - 14; break; case 'center': textX = config.x + config.width / 2; boxWidth = config.width; break; case 'right': textX = config.x + config.width - 8; boxWidth = config.width - 14; break; default: throw 'unreachable'; } // Clip the cell if the text is taller than the text box height. if (textHeight > boxHeight) { gc.beginPath(); gc.rect(config.x, config.y, config.width, config.height - 1); gc.clip(); } // Set the gc state. gc.font = font; gc.fillStyle = color; gc.textAlign = hAlign; gc.textBaseline = 'bottom'; // The current text width in pixels. let textWidth = gc.measureText(text).width; // Apply text wrapping if enabled. if (wrapText && textWidth > boxWidth) { // Make sure box clipping happens. gc.beginPath(); gc.rect(config.x, config.y, config.width, config.height - 1); gc.clip(); // Split column name to words based on // whitespace preceding a word boundary. // "Hello world" --> ["Hello ", "world"] const wordsInColumn = text.split(/\s(?=\b)/); // Y-coordinate offset for any additional lines let curY = textY; let textInCurrentLine = wordsInColumn.shift()!; // Single word. Applying text wrap on word by splitting // it into characters and fitting the maximum number of // characters possible per line (box width). if (wordsInColumn.length === 0) { let curLineTextWidth = gc.measureText(textInCurrentLine).width; while (curLineTextWidth > boxWidth && textInCurrentLine !== '') { // Iterating from the end of the string until we find a // substring (0,i) which has a width less than the box width. for (let i = textInCurrentLine.length; i > 0; i--) { const curSubString = textInCurrentLine.substring(0, i); const curSubStringWidth = gc.measureText(curSubString).width; if (curSubStringWidth < boxWidth || curSubString.length === 1) { // Found a substring which has a width less than the current // box width. Rendering that substring on the current line // and setting the remainder of the parent string as the next // string to iterate on for the next line. const nextLineText = textInCurrentLine.substring( i, textInCurrentLine.length ); textInCurrentLine = nextLineText; curLineTextWidth = gc.measureText(textInCurrentLine).width; gc.fillText(curSubString, textX, curY); curY += textHeight; // No need to continue iterating after we identified // an index to break the string on. break; } } } } // Multiple words in column header. Fitting maximum // number of words possible per line (box width). else { while (wordsInColumn.length !== 0) { // Processing the next word in the queue. const curWord = wordsInColumn.shift(); // Joining that word with the existing text for // the current line. const incrementedText = [textInCurrentLine, curWord].join(' '); const incrementedTextWidth = gc.measureText(incrementedText).width; if (incrementedTextWidth > boxWidth) { // If the newly combined text has a width larger than // the box width, we render the line before the current // word was added. We set the current word as the next // line. gc.fillText(textInCurrentLine, textX, curY); curY += textHeight; textInCurrentLine = curWord!; } else { // The combined text hasd a width less than the box width. We // set the the current line text to be the new combined text. textInCurrentLine = incrementedText; } } } gc.fillText(textInCurrentLine!, textX, curY); // Terminating the call here as we don't want // to apply text eliding when wrapping is active. return; } // Elide text that is too long let elide = '\u2026'; // Compute elided text if (elideDirection === 'right') { while (textWidth > boxWidth && text.length > 1) { if (text.length > 4 && textWidth >= 2 * boxWidth) { // If text width is substantially bigger, take half the string text = text.substring(0, text.length / 2 + 1) + elide; } else { // Otherwise incrementally remove the last character text = text.substring(0, text.length - 2) + elide; } textWidth = gc.measureText(text).width; } } else { while (textWidth > boxWidth && text.length > 1) { if (text.length > 4 && textWidth >= 2 * boxWidth) { // If text width is substantially bigger, take half the string text = elide + text.substring(text.length / 2); } else { // Otherwise incrementally remove the last character text = elide + text.substring(2); } textWidth = gc.measureText(text).width; } } // Draw the text for the cell. gc.fillText(text, textX, textY); } } /** * The namespace for the `TextRenderer` class statics. */ export namespace TextRenderer { /** * A type alias for the supported vertical alignment modes. */ export type VerticalAlignment = 'top' | 'center' | 'bottom'; /** * A type alias for the supported horizontal alignment modes. */ export type HorizontalAlignment = 'left' | 'center' | 'right'; /** * A type alias for the supported ellipsis sides. */ export type ElideDirection = 'left' | 'right'; /** * An options object for initializing a text renderer. */ export interface IOptions { /** * The font for drawing the cell text. * * The default is `'12px sans-serif'`. */ font?: CellRenderer.ConfigOption; /** * The color for the drawing the cell text. * * The default `'#000000'`. */ textColor?: CellRenderer.ConfigOption; /** * The background color for the cells. * * The default is `''`. */ backgroundColor?: CellRenderer.ConfigOption; /** * The vertical alignment for the cell text. * * The default is `'center'`. */ verticalAlignment?: CellRenderer.ConfigOption; /** * The horizontal alignment for the cell text. * * The default is `'left'`. */ horizontalAlignment?: CellRenderer.ConfigOption; /** * The format function for the renderer. * * The default is `TextRenderer.formatGeneric()`. */ format?: FormatFunc; /** * The ellipsis direction for the cell text. * * The default is `'right'`. */ elideDirection?: CellRenderer.ConfigOption; /** * Whether or not to apply text wrapping. * * The default is `'false'`. */ wrapText?: CellRenderer.ConfigOption; } /** * A type alias for a format function. */ export type FormatFunc = CellRenderer.ConfigFunc; /** * Create a generic text format function. * * @param options - The options for creating the format function. * * @returns A new generic text format function. * * #### Notes * This formatter uses the builtin `String()` to coerce any value * to a string. */ export function formatGeneric( options: formatGeneric.IOptions = {} ): FormatFunc { let missing = options.missing || ''; return ({ value }) => { if (value === null || value === undefined) { return missing; } return String(value); }; } /** * The namespace for the `formatGeneric` function statics. */ export namespace formatGeneric { /** * The options for creating a generic format function. */ export interface IOptions { /** * The text to use for a `null` or `undefined` data value. * * The default is `''`. */ missing?: string; } } /** * Create a fixed decimal format function. * * @param options - The options for creating the format function. * * @returns A new fixed decimal format function. * * #### Notes * This formatter uses the builtin `Number()` and `toFixed()` to * coerce values. * * The `formatIntlNumber()` formatter is more flexible, but slower. */ export function formatFixed(options: formatFixed.IOptions = {}): FormatFunc { let digits = options.digits; let missing = options.missing || ''; return ({ value }) => { if (value === null || value === undefined) { return missing; } return Number(value).toFixed(digits); }; } /** * The namespace for the `formatFixed` function statics. */ export namespace formatFixed { /** * The options for creating a fixed format function. */ export interface IOptions { /** * The number of digits to include after the decimal point. * * The default is determined by the user agent. */ digits?: number; /** * The text to use for a `null` or `undefined` data value. * * The default is `''`. */ missing?: string; } } /** * Create a significant figure format function. * * @param options - The options for creating the format function. * * @returns A new significant figure format function. * * #### Notes * This formatter uses the builtin `Number()` and `toPrecision()` * to coerce values. * * The `formatIntlNumber()` formatter is more flexible, but slower. */ export function formatPrecision( options: formatPrecision.IOptions = {} ): FormatFunc { let digits = options.digits; let missing = options.missing || ''; return ({ value }) => { if (value === null || value === undefined) { return missing; } return Number(value).toPrecision(digits); }; } /** * The namespace for the `formatPrecision` function statics. */ export namespace formatPrecision { /** * The options for creating a precision format function. */ export interface IOptions { /** * The number of significant figures to include in the value. * * The default is determined by the user agent. */ digits?: number; /** * The text to use for a `null` or `undefined` data value. * * The default is `''`. */ missing?: string; } } /** * Create a scientific notation format function. * * @param options - The options for creating the format function. * * @returns A new scientific notation format function. * * #### Notes * This formatter uses the builtin `Number()` and `toExponential()` * to coerce values. * * The `formatIntlNumber()` formatter is more flexible, but slower. */ export function formatExponential( options: formatExponential.IOptions = {} ): FormatFunc { let digits = options.digits; let missing = options.missing || ''; return ({ value }) => { if (value === null || value === undefined) { return missing; } return Number(value).toExponential(digits); }; } /** * The namespace for the `formatExponential` function statics. */ export namespace formatExponential { /** * The options for creating an exponential format function. */ export interface IOptions { /** * The number of digits to include after the decimal point. * * The default is determined by the user agent. */ digits?: number; /** * The text to use for a `null` or `undefined` data value. * * The default is `''`. */ missing?: string; } } /** * Create an international number format function. * * @param options - The options for creating the format function. * * @returns A new international number format function. * * #### Notes * This formatter uses the builtin `Intl.NumberFormat` object to * coerce values. * * This is the most flexible (but slowest) number formatter. */ export function formatIntlNumber( options: formatIntlNumber.IOptions = {} ): FormatFunc { let missing = options.missing || ''; let nft = new Intl.NumberFormat(options.locales, options.options); return ({ value }) => { if (value === null || value === undefined) { return missing; } return nft.format(value); }; } /** * The namespace for the `formatIntlNumber` function statics. */ export namespace formatIntlNumber { /** * The options for creating an intl number format function. */ export interface IOptions { /** * The locales to pass to the `Intl.NumberFormat` constructor. * * The default is determined by the user agent. */ locales?: string | string[]; /** * The options to pass to the `Intl.NumberFormat` constructor. * * The default is determined by the user agent. */ options?: Intl.NumberFormatOptions; /** * The text to use for a `null` or `undefined` data value. * * The default is `''`. */ missing?: string; } } /** * Create a date format function. * * @param options - The options for creating the format function. * * @returns A new date format function. * * #### Notes * This formatter uses `Date.toDateString()` to format the values. * * If a value is not a `Date` object, `new Date(value)` is used to * coerce the value to a date. * * The `formatIntlDateTime()` formatter is more flexible, but slower. */ export function formatDate(options: formatDate.IOptions = {}): FormatFunc { let missing = options.missing || ''; return ({ value }) => { if (value === null || value === undefined) { return missing; } if (value instanceof Date) { return value.toDateString(); } return new Date(value).toDateString(); }; } /** * The namespace for the `formatDate` function statics. */ export namespace formatDate { /** * The options for creating a date format function. */ export interface IOptions { /** * The text to use for a `null` or `undefined` data value. * * The default is `''`. */ missing?: string; } } /** * Create a time format function. * * @param options - The options for creating the format function. * * @returns A new time format function. * * #### Notes * This formatter uses `Date.toTimeString()` to format the values. * * If a value is not a `Date` object, `new Date(value)` is used to * coerce the value to a date. * * The `formatIntlDateTime()` formatter is more flexible, but slower. */ export function formatTime(options: formatTime.IOptions = {}): FormatFunc { let missing = options.missing || ''; return ({ value }) => { if (value === null || value === undefined) { return missing; } if (value instanceof Date) { return value.toTimeString(); } return new Date(value).toTimeString(); }; } /** * The namespace for the `formatTime` function statics. */ export namespace formatTime { /** * The options for creating a time format function. */ export interface IOptions { /** * The text to use for a `null` or `undefined` data value. * * The default is `''`. */ missing?: string; } } /** * Create an ISO datetime format function. * * @param options - The options for creating the format function. * * @returns A new ISO datetime format function. * * #### Notes * This formatter uses `Date.toISOString()` to format the values. * * If a value is not a `Date` object, `new Date(value)` is used to * coerce the value to a date. * * The `formatIntlDateTime()` formatter is more flexible, but slower. */ export function formatISODateTime( options: formatISODateTime.IOptions = {} ): FormatFunc { let missing = options.missing || ''; return ({ value }) => { if (value === null || value === undefined) { return missing; } if (value instanceof Date) { return value.toISOString(); } return new Date(value).toISOString(); }; } /** * The namespace for the `formatISODateTime` function statics. */ export namespace formatISODateTime { /** * The options for creating an ISO datetime format function. */ export interface IOptions { /** * The text to use for a `null` or `undefined` data value. * * The default is `''`. */ missing?: string; } } /** * Create a UTC datetime format function. * * @param options - The options for creating the format function. * * @returns A new UTC datetime format function. * * #### Notes * This formatter uses `Date.toUTCString()` to format the values. * * If a value is not a `Date` object, `new Date(value)` is used to * coerce the value to a date. * * The `formatIntlDateTime()` formatter is more flexible, but slower. */ export function formatUTCDateTime( options: formatUTCDateTime.IOptions = {} ): FormatFunc { let missing = options.missing || ''; return ({ value }) => { if (value === null || value === undefined) { return missing; } if (value instanceof Date) { return value.toUTCString(); } return new Date(value).toUTCString(); }; } /** * The namespace for the `formatUTCDateTime` function statics. */ export namespace formatUTCDateTime { /** * The options for creating a UTC datetime format function. */ export interface IOptions { /** * The text to use for a `null` or `undefined` data value. * * The default is `''`. */ missing?: string; } } /** * Create an international datetime format function. * * @param options - The options for creating the format function. * * @returns A new international datetime format function. * * #### Notes * This formatter uses the builtin `Intl.DateTimeFormat` object to * coerce values. * * This is the most flexible (but slowest) datetime formatter. */ export function formatIntlDateTime( options: formatIntlDateTime.IOptions = {} ): FormatFunc { let missing = options.missing || ''; let dtf = new Intl.DateTimeFormat(options.locales, options.options); return ({ value }) => { if (value === null || value === undefined) { return missing; } return dtf.format(value); }; } /** * The namespace for the `formatIntlDateTime` function statics. */ export namespace formatIntlDateTime { /** * The options for creating an intl datetime format function. */ export interface IOptions { /** * The locales to pass to the `Intl.DateTimeFormat` constructor. * * The default is determined by the user agent. */ locales?: string | string[]; /** * The options to pass to the `Intl.DateTimeFormat` constructor. * * The default is determined by the user agent. */ options?: Intl.DateTimeFormatOptions; /** * The text to use for a `null` or `undefined` data value. * * The default is `''`. */ missing?: string; } } /** * Measure the height of a font. * * @param font - The CSS font string of interest. * * @returns The height of the font bounding box. * * #### Notes * This function uses a temporary DOM node to measure the text box * height for the specified font. The first call for a given font * will incur a DOM reflow, but the return value is cached, so any * subsequent call for the same font will return the cached value. */ export function measureFontHeight(font: string): number { // Look up the cached font height. let height = Private.fontHeightCache[font]; // Return the cached font height if it exists. if (height !== undefined) { return height; } // Normalize the font. Private.fontMeasurementGC.font = font; let normFont = Private.fontMeasurementGC.font; // Set the font on the measurement node. Private.fontMeasurementNode.style.font = normFont; // Add the measurement node to the document. document.body.appendChild(Private.fontMeasurementNode); // Measure the node height. height = Private.fontMeasurementNode.offsetHeight; // Remove the measurement node from the document. document.body.removeChild(Private.fontMeasurementNode); // Cache the measured height for the font and norm font. Private.fontHeightCache[font] = height; Private.fontHeightCache[normFont] = height; // Return the measured height. return height; } } /** * The namespace for the module implementation details. */ namespace Private { /** * A cache of measured font heights. */ export const fontHeightCache: { [font: string]: number } = Object.create( null ); /** * The DOM node used for font height measurement. */ export const fontMeasurementNode = (() => { let node = document.createElement('div'); node.style.position = 'absolute'; node.style.top = '-99999px'; node.style.left = '-99999px'; node.style.visibility = 'hidden'; node.textContent = 'M'; return node; })(); /** * The GC used for font measurement. */ export const fontMeasurementGC = (() => { let canvas = document.createElement('canvas'); canvas.width = 0; canvas.height = 0; return canvas.getContext('2d')!; })(); } lumino-2021.12.13/packages/datagrid/tdoptions.json000066400000000000000000000005171415564225700216770ustar00rootroot00000000000000{ "excludeNotExported": true, "mode": "file", "target": "es5", "module": "es5", "lib": [ "lib.es2015.d.ts", "lib.es2015.collection.d.ts", "lib.es2015.promise.d.ts", "lib.dom.d.ts" ], "out": "../../docs/source/api/datagrid", "baseUrl": ".", "paths": { "@lumino/*": ["node_modules/@lumino/*"] } } lumino-2021.12.13/packages/datagrid/tsconfig.json000066400000000000000000000015611415564225700214700ustar00rootroot00000000000000{ "compilerOptions": { "composite": true, "sourceMap": true, "declaration": true, "declarationDir": "./types", "declarationMap": true, "noImplicitAny": true, "noEmitOnError": true, "noUnusedLocals": true, "strictNullChecks": true, "module": "ES6", "moduleResolution": "node", "target": "ES5", "outDir": "lib", "lib": ["ES5", "ES2015.Collection", "ES2015.Iterable", "DOM"], "importHelpers": true, "types": [], "rootDir": "src" }, "include": ["src/*"], "references": [ { "path": "../algorithm" }, { "path": "../coreutils" }, { "path": "../disposable" }, { "path": "../domutils" }, { "path": "../dragdrop" }, { "path": "../messaging" }, { "path": "../signaling" }, { "path": "../widgets" } ] } lumino-2021.12.13/packages/datastore/000077500000000000000000000000001415564225700171655ustar00rootroot00000000000000lumino-2021.12.13/packages/datastore/api-extractor.json000066400000000000000000000014611415564225700226440ustar00rootroot00000000000000/** * Config file for API Extractor. For more info, please visit: https://api-extractor.com */ { "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", /** * Optionally specifies another JSON config file that this file extends from. This provides a way for * standard settings to be shared across multiple projects. * * If the path starts with "./" or "../", the path is resolved relative to the folder of the file that contains * the "extends" field. Otherwise, the first path segment is interpreted as an NPM package name, and will be * resolved using NodeJS require(). * * SUPPORTED TOKENS: none * DEFAULT VALUE: "" */ "extends": "../../api-extractor-base.json" // "extends": "my-package/include/api-extractor-base.json" } lumino-2021.12.13/packages/datastore/package.json000066400000000000000000000045271415564225700214630ustar00rootroot00000000000000{ "name": "@lumino/datastore", "version": "0.17.1", "description": "Lumino DataStore", "homepage": "https://github.com/jupyterlab/lumino", "bugs": { "url": "https://github.com/jupyterlab/lumino/issues" }, "repository": { "type": "git", "url": "https://github.com/jupyterlab/lumino.git" }, "license": "BSD-3-Clause", "author": "S. Chris Colbert ", "contributors": [ "A. Darian ", "S. Chris Colbert ", "Steven Silvester ", "Vidar T. Fauske ", "Ian Rose " ], "main": "dist/index.js", "jsdelivr": "dist/index.min.js", "unpkg": "dist/index.min.js", "module": "dist/index.es6", "types": "types/index.d.ts", "files": [ "dist/*", "src/*", "types/*" ], "scripts": { "api": "api-extractor run --local --verbose", "build": "npm run build:src && rollup -c", "build:src": "tsc --build", "build:test": "tsc --build tests && cd tests && webpack", "clean": "rimraf ./lib && rimraf *.tsbuildinfo && rimraf ./types && rimraf ./dist", "docs": "typedoc --options tdoptions.json src", "minimize": "terser dist/index.js -c -m --source-map \"content='dist/index.js.map',url='index.min.js.map'\" -o dist/index.min.js", "test": "npm run test:firefox-headless", "test:chrome": "cd tests && karma start --browsers=Chrome", "test:chrome-headless": "cd tests && karma start --browsers=ChromeHeadless", "test:firefox": "cd tests && karma start --browsers=Firefox", "test:firefox-headless": "cd tests && karma start --browsers=FirefoxHeadless", "test:ie": "cd tests && karma start --browsers=IE", "watch": "tsc --build --watch" }, "dependencies": { "@lumino/algorithm": "^1.9.1", "@lumino/collections": "^1.9.1", "@lumino/coreutils": "^1.11.1", "@lumino/disposable": "^1.10.1", "@lumino/messaging": "^1.10.1", "@lumino/signaling": "^1.10.1" }, "devDependencies": { "rimraf": "^3.0.2", "rollup": "^2.56.0", "rollup-plugin-node-resolve": "^5.2.0", "rollup-plugin-postcss": "^4.0.0", "rollup-plugin-sourcemaps": "^0.6.3", "terser": "^5.7.1", "tslib": "^2.3.0", "typedoc": "~0.15.0", "typescript": "~3.6.0", "webpack-cli": "^3.3.10" }, "publishConfig": { "access": "public" } } lumino-2021.12.13/packages/datastore/rollup.config.js000066400000000000000000000015231415564225700223050ustar00rootroot00000000000000import nodeResolve from 'rollup-plugin-node-resolve'; import sourcemaps from 'rollup-plugin-sourcemaps'; import postcss from 'rollup-plugin-postcss'; const pkg = require('./package.json'); const globals = id => id.indexOf('@lumino/') === 0 ? id.replace('@lumino/', 'lumino_') : id; export default [ { input: 'lib/index', external: id => pkg.dependencies && !!pkg.dependencies[id], output: [ { file: pkg.main, globals, format: 'umd', sourcemap: true, name: pkg.name }, { file: pkg.module + '.js', format: 'es', sourcemap: true, name: pkg.name } ], plugins: [ nodeResolve({ preferBuiltins: true }), sourcemaps(), postcss({ extensions: ['.css'], minimize: true }) ] } ]; lumino-2021.12.13/packages/datastore/src/000077500000000000000000000000001415564225700177545ustar00rootroot00000000000000lumino-2021.12.13/packages/datastore/src/datastore.ts000066400000000000000000000714271415564225700223250ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2019, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { each, IIterable, IIterator, iterItems, map, StringExt, toArray, toObject } from '@lumino/algorithm'; import { BPlusTree, LinkedList } from '@lumino/collections'; import { DisposableDelegate, IDisposable } from '@lumino/disposable'; import { ConflatableMessage, IMessageHandler, Message, MessageLoop } from '@lumino/messaging'; import { ISignal, Signal } from '@lumino/signaling'; import { Record } from './record'; import { Schema, validateSchema } from './schema'; import { IServerAdapter } from './serveradapter'; import { Table } from './table'; import { createDuplexId } from './utilities'; /** * A multi-user collaborative datastore. * * #### Notes * A store is structured in a maximally flat way using a hierarchy * of tables, records, and fields. Internally, the object graph is * synchronized among all users via CRDT algorithms. * * https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type * https://hal.inria.fr/file/index/docid/555588/filename/techreport.pdf */ export class Datastore implements IDisposable, IIterable>, IMessageHandler { /** * Create a new datastore. * * @param options - The options for creating the datastore * * @returns A new datastore table. * * @throws An exception if any of the schema definitions are invalid. */ static create(options: Datastore.IOptions): Datastore { let { schemas } = options; // Throws an error for invalid schemas: Private.validateSchemas(schemas); let context = { inTransaction: false, transactionId: '', version: 0, storeId: options.id, change: {}, patch: {} }; let tables = new BPlusTree>(Private.recordCmp); if (options.restoreState) { // If passed state to restore, pass the intital state to recreate each // table let state = JSON.parse(options.restoreState); tables.assign( map(schemas, s => { return Table.recreate(s, context, state[s.id] || []); }) ); } else { // Otherwise, simply create a new, empty table tables.assign( map(schemas, s => { return Table.create(s, context); }) ); } return new Datastore(context, tables, options.adapter); } /** * Dispose of the resources held by the datastore. */ dispose(): void { // Bail if already disposed. if (this._disposed) { return; } this._disposed = true; Signal.clearData(this); this._adapter = null; } /** * Whether the datastore has been disposed. */ get isDisposed(): boolean { return this._disposed; } /** * A signal emitted when changes are made to the store. * * #### Notes * This signal is emitted either at the end of a local mutation, * or after a remote mutation has been applied. The storeId can * be used to determine its source. * * The payload represents the set of local changes that were made * to bring the store to its current state. * * #### Complexity * `O(1)` */ get changed(): ISignal { return this._changed; } /** * The unique id of the store. * * #### Notes * The id is unique among all other collaborating peers. * * #### Complexity * `O(1)` */ get id(): number { return this._context.storeId; } /** * Whether a transaction is currently in progress. * * #### Complexity * `O(1)` */ get inTransaction(): boolean { return this._context.inTransaction; } /** * The current version of the datastore. * * #### Notes * This version is automatically increased for each transaction * to the store. However, it might not increase linearly (i.e. * it might make jumps). * * #### Complexity * `O(1)` */ get version(): number { return this._context.version; } /** * Create an iterator over all the tables of the datastore. * * @returns An iterator. */ iter(): IIterator> { return this._tables.iter(); } /** * Get the table for a particular schema. * * @param schema - The schema of interest. * * @returns The table for the specified schema. * * @throws An exception if no table exists for the given schema. * * #### Complexity * `O(log32 n)` */ get(schema: S): Table { let t = this._tables.get(schema.id, Private.recordIdCmp); if (t === undefined) { throw new Error(`No table found for schema with id: ${schema.id}`); } return t as Table; } /** * Begin a new transaction in the store. * * @returns The id of the new transaction * * @throws An exception if a transaction is already in progress. * * #### Notes * This will allow the state of the store to be mutated * thorugh the `update` method on the individual tables. * * After the updates are completed, `endTransaction` should * be called. */ beginTransaction(): string { let newVersion = this._context.version + 1; let id = this._transactionIdFactory(newVersion, this.id); this._initTransaction(id, newVersion); MessageLoop.postMessage(this, new ConflatableMessage('transaction-begun')); return id; } /** * Completes a transaction. * * #### Notes * This completes a transaction previously started with * `beginTransaction`. If a change has occurred, the * `changed` signal will be emitted. */ endTransaction(): void { this._finalizeTransaction(); let { patch, change, storeId, transactionId, version } = this._context; // Possibly broadcast the transaction to collaborators. if (this._adapter && !Private.isPatchEmpty(patch)) { this._adapter.broadcast({ id: transactionId, storeId, patch, version }); } // Add the transation to the cemetery to indicate it is visible. this._cemetery[transactionId] = 1; // Emit a change signal if (!Private.isChangeEmpty(this._context.change)) { this._changed.emit({ storeId, transactionId, type: 'transaction', change }); } } /** * Handle a message. */ processMessage(msg: Message): void { switch (msg.type) { case 'transaction-begun': if (this._context.inTransaction) { console.warn( `Automatically ending transaction (did you forget to end it?): ${this._context.transactionId}` ); this.endTransaction(); } break; case 'queued-transaction': this._processQueue(); break; default: break; } } /** * Undo a patch that was previously applied. * * @param transactionId - The transaction to undo. * * @returns A promise which resolves when the action is complete. * * @throws An exception if `undo` is called during a mutation, or if no * server adapter has been set for the datastore. * * #### Notes * If changes are made, the `changed` signal will be emitted before * the promise resolves. */ undo(transactionId: string): Promise { if (!this._adapter) { throw Error('No server adapter has been set for the datastore'); } if (this.inTransaction) { throw Error('Cannot undo during a transaction'); } return this._adapter.undo(transactionId); } /** * Redo a patch that was previously undone. * * @param transactionId - The transaction to redo. * * @returns A promise which resolves when the action is complete. * * @throws An exception if `redo` is called during a mutation, or if no * server adapter has been set for the datastore. * * #### Notes * If changes are made, the `changed` signal will be emitted before * the promise resolves. */ redo(transactionId: string): Promise { if (!this._adapter) { throw Error('No server adapter has been set for the datastore'); } if (this.inTransaction) { throw Error('Cannot redo during a transaction'); } return this._adapter.redo(transactionId); } /** * The handler for broadcasting transactions to peers. */ get adapter(): IServerAdapter | null { return this._adapter; } /** * Serialize the state of the datastore to a string. * * @returns The serialized state. */ toString(): string { return JSON.stringify( toObject( map(this, (table): [string, Record[]] => { return [table.schema.id, toArray(table)]; }) ) ); } /** * Create a new datastore. * * @param id - The unique id of the datastore. * @param tables - The tables of the datastore. */ private constructor( context: Datastore.Context, tables: BPlusTree>, adapter?: IServerAdapter, transactionIdFactory?: Datastore.TransactionIdFactory ) { this._context = context; this._tables = tables; this._adapter = adapter || null; this._transactionIdFactory = transactionIdFactory || createDuplexId; if (this._adapter) { this._adapter.onRemoteTransaction = this._onRemoteTransaction.bind(this); this._adapter.onUndo = this._onUndo.bind(this); this._adapter.onRedo = this._onRedo.bind(this); } } /** * Handle a transaction from the server adapter. */ private _onRemoteTransaction(transaction: Datastore.Transaction): void { this._processTransaction(transaction, 'transaction'); } /** * Handle an undo from the server adapter. */ private _onUndo(transaction: Datastore.Transaction): void { this._processTransaction(transaction, 'undo'); } /** * Handle a redo from the server adapter. */ private _onRedo(transaction: Datastore.Transaction): void { this._processTransaction(transaction, 'redo'); } /** * Apply a transaction to the datastore. * * @param transactionApplication - The data of the transaction. * * @throws An exception if `processTransaction` is called during a mutation. * * #### Notes * If changes are made, the `changed` signal will be emitted. */ private _processTransaction( transaction: Datastore.Transaction, type: Datastore.TransactionType ): void { let { storeId, patch } = transaction; try { this._initTransaction( transaction.id, Math.max(this._context.version, transaction.version) ); } catch (e) { // Already in a transaction. Put the transaction in the queue to apply // later. this._queueTransaction(transaction, type); return; } let change: Datastore.MutableChange = {}; try { each(iterItems(patch), ([schemaId, tablePatch]) => { let table = this._tables.get(schemaId, Private.recordIdCmp); if (table === undefined) { console.warn( `Missing table for schema id '${schemaId}' in transaction '${transaction.id}'` ); this._finalizeTransaction(); return; } if (type === 'transaction' || type === 'redo') { let count = this._cemetery[transaction.id]; if (count === undefined) { this._cemetery[transaction.id] = 1; change[schemaId] = Table.patch(table, tablePatch); return; } this._cemetery[transaction.id] = count + 1; // If the transaction is just now positive, apply it to the store. if (this._cemetery[transaction.id] === 1) { change[schemaId] = Table.patch(table, tablePatch); return; } } else { let count = this._cemetery[transaction.id]; if (count === undefined) { this._cemetery[transaction.id] = -1; return; } this._cemetery[transaction.id] = count - 1; // If the transaction hasn't already been unapplied, do so. if (this._cemetery[transaction.id] === 0) { change[schemaId] = Table.unpatch(table, tablePatch); } } }); } finally { this._finalizeTransaction(); } if (!Private.isChangeEmpty(change)) { this._changed.emit({ storeId, transactionId: transaction.id, type, change }); } } /** * Queue a transaction for later application. * * @param transaction - the transaction to queue. */ private _queueTransaction( transaction: Datastore.Transaction, type: Datastore.TransactionType ): void { this._transactionQueue.addLast([transaction, type]); MessageLoop.postMessage(this, new ConflatableMessage('queued-transaction')); } /** * Process all transactions currently queued. */ private _processQueue(): void { let queue = this._transactionQueue; // If the transaction queue is empty, bail. if (queue.isEmpty) { return; } // Add a sentinel value to the end of the queue. The queue will // only be processed up to the sentinel. Transactions added during // this cycle will execute on the next cycle. let sentinel = {}; queue.addLast(sentinel as any); // Enter the processing loop. // eslint-disable-next-line no-constant-condition while (true) { // Remove the first transaction in the queue. let [transaction, type] = queue.removeFirst()!; // If the value is the sentinel, exit the loop. if (transaction === sentinel) { return; } // Apply the transaction. this._processTransaction(transaction, type); } } /** * Reset the context state for a new transaction. * * @param id - The id of the new transaction. * @param newVersion - The version of the datastore after the transaction. * * @throws An exception if a transaction is already in progress. */ private _initTransaction(id: string, newVersion: number): void { let context = this._context as Private.MutableContext; if (context.inTransaction) { throw new Error( `Already in a transaction: ${this._context.transactionId}` ); } context.inTransaction = true; context.change = {}; context.patch = {}; context.transactionId = id; context.version = newVersion; } /** * Finalize the context state for a transaction in progress. * * @throws An exception if no transaction is in progress. */ private _finalizeTransaction(): void { let context = this._context as Private.MutableContext; if (!context.inTransaction) { throw new Error('No transaction in progress.'); } context.inTransaction = false; } private _adapter: IServerAdapter | null; private _cemetery: { [id: string]: number } = {}; private _disposed = false; private _tables: BPlusTree>; private _context: Datastore.Context; private _changed = new Signal(this); private _transactionIdFactory: Datastore.TransactionIdFactory; private _transactionQueue = new LinkedList< [Datastore.Transaction, Datastore.TransactionType] >(); } /** * The namespace for the `Datastore` class statics. */ export namespace Datastore { /** * A type alias for kinds of transactions. */ export type TransactionType = 'transaction' | 'undo' | 'redo'; /** * An options object for initializing a datastore. */ export interface IOptions { /** * The unique id of the datastore. */ id: number; /** * The table schemas of the datastore. */ schemas: ReadonlyArray; /** * An optional handler for broadcasting transactions to peers. */ adapter?: IServerAdapter; /** * An optional transaction id factory to override the default. */ transactionIdFactory?: TransactionIdFactory; /** * Initialize the state to a previously serialized one. */ restoreState?: string; } /** * The arguments object for the store `changed` signal. */ export interface IChangedArgs { /** * Whether the change was generated by transaction, undo, or redo. */ readonly type: TransactionType; /** * The transaction id associated with the change. */ readonly transactionId: string; /** * The id of the store responsible for the change. */ readonly storeId: number; /** * A mapping of schema id to table change set. */ readonly change: Change; } /** * A type alias for a store change. */ export type Change = { readonly [schemaId: string]: Table.Change; }; /** * A type alias for a store patch. */ export type Patch = { readonly [schemaId: string]: Table.Patch; }; /** * @internal */ export type MutableChange = { [schemaId: string]: Table.MutableChange; }; /** * @internal */ export type MutablePatch = { [schemaId: string]: Table.MutablePatch; }; /** * An object representing a datastore transaction. */ export type Transaction = { /** * The id of the transaction. */ readonly id: string; /** * The id of the store responsible for the transaction. */ readonly storeId: number; /** * The patch data of the transaction. */ readonly patch: Patch; /** * The version of the source datastore. */ readonly version: number; }; /** * @internal */ export type Context = Readonly; /** * A factory function for generating a unique transaction id. */ export type TransactionIdFactory = ( version: number, storeId: number ) => string; /** * A helper function to wrap an update to the datastore in calls to * `beginTransaction` and `endTransaction`. * * @param datastore: the datastore to which to apply the update. * * @param update: A function that performs the update on the datastore. * The function is called with a transaction id string, in case the * user wishes to store the transaction ID for later use. * * @returns the transaction ID. * * #### Notes * If the datastore is already in a transaction, this does not attempt * to start a new one, and returns an empty string for the transaction * id. This allows for transactions to be composed a bit more easily. */ export function withTransaction( datastore: Datastore, update: (id: string) => void ): string { let id = ''; if (!datastore.inTransaction) { id = datastore.beginTransaction(); } try { update(id); } finally { if (id) { datastore.endTransaction(); } } return id; } /** * A base type for describing the location of data in a datastore, * to be consumed by some object. The only requirement is that it * has a datastore object. Objects extending from this will, in general, * have some combination of table, record, and field locations. */ export type DataLocation = { /** * The datastore in which the data is contained. */ datastore: Datastore; }; /** * An interface for referring to a specific table in a datastore. */ export type TableLocation = { /** * The schema in question. This schema must exist in the datastore, * or an error may result in its usage. */ schema: S; }; /** * An interface for referring to a specific record in a datastore. */ export type RecordLocation = TableLocation & { /** * The record in question. */ record: string; }; /** * An interface for referring to a specific field in a datastore. * * #### Notes * The field must exist in the schema. */ export type FieldLocation< S extends Schema, F extends keyof S['fields'] > = RecordLocation & { /** * The field in question. */ field: F; }; /** * Get a given table by its location. * * @param datastore: the datastore in which the table resides. * * @param loc: The table location. * * @returns the table. */ export function getTable( datastore: Datastore, loc: TableLocation ): Table { return datastore.get(loc.schema); } /** * Get a given record by its location. * * @param datastore: the datastore in which the record resides. * * @param loc: The record location. * * @returns the record, or undefined if it does not exist. */ export function getRecord( datastore: Datastore, loc: RecordLocation ): Record.Value | undefined { return datastore.get(loc.schema).get(loc.record); } /** * Get a given field by its location. * * @param datastore: the datastore in which the field resides. * * @param loc: the field location. * * @returns the field in question. * * #### Notes * This will throw an error if the record does not exist in the given table. */ export function getField( datastore: Datastore, loc: FieldLocation ): S['fields'][F]['ValueType'] { const record = datastore.get(loc.schema).get(loc.record); if (!record) { throw Error(`The record ${loc.record} could not be found`); } return record[loc.field]; } /** * Update a table. * * @param datastore: the datastore in which the table resides. * * @param loc: the table location. * * @param update: the update to the table. * * #### Notes * This does not begin a transaction, so usage of this function should be * combined with `beginTransaction`/`endTransaction`, or `withTransaction`. */ export function updateTable( datastore: Datastore, loc: TableLocation, update: Table.Update ): void { let table = datastore.get(loc.schema); table.update(update); } /** * Update a record in a table. * * @param datastore: the datastore in which the record resides. * * @param loc: the record location. * * @param update: the update to the record. * * #### Notes * This does not begin a transaction, so usage of this function should be * combined with `beginTransaction`/`endTransaction`, or `withTransaction`. */ export function updateRecord( datastore: Datastore, loc: RecordLocation, update: Record.Update ): void { let table = datastore.get(loc.schema); table.update({ [loc.record]: update }); } /** * Update a field in a table. * * @param datastore: the datastore in which the field resides. * * @param loc: the field location. * * @param update: the update to the field. * * #### Notes * This does not begin a transaction, so usage of this function should be * combined with `beginTransaction`/`endTransaction`, or `withTransaction`. */ export function updateField( datastore: Datastore, loc: FieldLocation, update: S['fields'][F]['UpdateType'] ): void { let table = datastore.get(loc.schema); // TODO: this cast may be made unnecessary once microsoft/TypeScript#13573 // is fixed, possibly by microsoft/TypeScript#26797 lands. table.update({ [loc.record]: { [loc.field]: update } as Record.Update }); } /** * Listen to changes in a table. Changes to other tables are ignored. * * @param datastore: the datastore in which the table resides. * * @param loc: the table location. * * @param slot: a callback function to invoke when the table changes. * * @returns an `IDisposable` that can be disposed to remove the listener. */ export function listenTable( datastore: Datastore, loc: TableLocation, slot: (source: Datastore, args: Table.Change) => void, thisArg?: any ): IDisposable { // A wrapper change signal connection function. const wrapper = (source: Datastore, args: Datastore.IChangedArgs) => { // Ignore changes that don't match the requested record. if (!args.change[loc.schema.id]) { return; } // Otherwise, call the slot. const tc = args.change[loc.schema.id]! as Table.Change; slot.bind(thisArg)(source, tc); }; datastore.changed.connect(wrapper); return new DisposableDelegate(() => { datastore.changed.disconnect(wrapper); }); } /** * Listen to changes in a record in a table. Changes to other tables and * other records in the same table are ignored. * * @param datastore: the datastore in which the record resides. * * @param loc: the record location. * * @param slot: a callback function to invoke when the record changes. * * @returns an `IDisposable` that can be disposed to remove the listener. */ export function listenRecord( datastore: Datastore, loc: RecordLocation, slot: (source: Datastore, args: Record.Change) => void, thisArg?: any ): IDisposable { // A wrapper change signal connection function. const wrapper = (source: Datastore, args: Datastore.IChangedArgs) => { // Ignore changes that don't match the requested record. if ( !args.change[loc.schema.id] || !args.change[loc.schema.id][loc.record] ) { return; } // Otherwise, call the slot. const tc = args.change[loc.schema.id]! as Table.Change; slot.bind(thisArg)(source, tc[loc.record]); }; datastore.changed.connect(wrapper); return new DisposableDelegate(() => { datastore.changed.disconnect(wrapper); }); } /** * Listen to changes in a fields in a table. Changes to other tables, other * records in the same table, and other fields in the same record are ignored. * * @param datastore: the datastore in which the field resides. * * @param loc: the field location. * * @param slot: a callback function to invoke when the field changes. * * @returns an `IDisposable` that can be disposed to remove the listener. */ export function listenField( datastore: Datastore, loc: FieldLocation, slot: (source: Datastore, args: S['fields'][F]['ChangeType']) => void, thisArg?: any ): IDisposable { const wrapper = (source: Datastore, args: Datastore.IChangedArgs) => { // Ignore changes that don't match the requested field. if ( !args.change[loc.schema.id] || !args.change[loc.schema.id][loc.record] || !args.change[loc.schema.id][loc.record][loc.field as string] ) { return; } // Otherwise, call the slot. const tc = args.change[loc.schema.id]! as Table.Change; slot.bind(thisArg)(source, tc[loc.record][loc.field]); }; datastore.changed.connect(wrapper); return new DisposableDelegate(() => { datastore.changed.disconnect(wrapper); }); } } namespace Private { /** * Validates all schemas, and throws an error if any are invalid. */ export function validateSchemas(schemas: ReadonlyArray) { let errors = []; for (let s of schemas) { let err = validateSchema(s); if (err.length) { errors.push(`Schema '${s.id}' validation failed: \n${err.join('\n')}`); } } if (errors.length) { throw new Error(errors.join('\n\n')); } } /** * A three-way record comparison function. */ export function recordCmp( a: Table, b: Table ): number { return StringExt.cmp(a.schema.id, b.schema.id); } /** * A three-way record id comparison function. */ export function recordIdCmp( table: Table, id: string ): number { return StringExt.cmp(table.schema.id, id); } export type MutableContext = { /** * Whether the datastore currently in a transaction. */ inTransaction: boolean; /** * The id of the current transaction. */ transactionId: string; /** * The current version of the datastore. */ version: number; /** * The unique id of the datastore. */ storeId: number; /** * The current change object of the transaction. */ change: Datastore.MutableChange; /** * The current patch object of the transaction. */ patch: Datastore.MutablePatch; }; /** * Checks if a patch is empty. */ export function isPatchEmpty(patch: Datastore.Patch): boolean { return Object.keys(patch).length === 0; } /** * Checks if a change is empty. */ export function isChangeEmpty(change: Datastore.Change): boolean { return Object.keys(change).length === 0; } } lumino-2021.12.13/packages/datastore/src/field.ts000066400000000000000000000141621415564225700214130ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2018, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { ReadonlyJSONValue } from '@lumino/coreutils'; /** * An abstract base class for datastore field types. */ export abstract class Field< Value extends ReadonlyJSONValue, Update extends ReadonlyJSONValue, Metadata extends ReadonlyJSONValue, Change extends ReadonlyJSONValue, Patch extends ReadonlyJSONValue > { /** * Construct a new field. * * @param options - The options for initializing the field. */ constructor(options: Field.IOptions = {}) { let opts = { description: '', ...options }; this.description = opts.description; } /** * The human-readable description of the field. */ readonly description: string; /** * The value type for the field. * * #### Notes * This type represents the user-facing value stored in the record. */ readonly ValueType: Value; /** * The update type for the field. * * #### Notes * This type represents the data the user passes to the `update` * method of a table to update the field of a particular record. */ readonly UpdateType: Update; /** * The metadata type for the field. * * #### Notes * This type represents extra bookeeping data needed by the field * to accurately apply updates and patches. * * This type extends the `ReadonlyJSONValue` type so that it may * hold readonly JSON data. However, the metadata is intended to * be mutated in-place and thus may also contain mutable data. */ readonly MetadataType: Metadata; /** * The change type for the field. * * #### Notes * This type represents the user-facing change to the field's value. */ readonly ChangeType: Change; /** * The patch type for the field. * * #### Notes * This type represents the system-facing patch to the field's value. */ readonly PatchType: Patch; /** * The discriminated type name for the field. */ abstract readonly type: string; /** * Create the initial value for the field. * * @returns The initial value for the field. */ abstract createValue(): Value; /** * Create the metadata for the field. * * @returns The metadata for the field. */ abstract createMetadata(): Metadata; /** * Apply a user update to the field. * * @param args - The arguments for the update. * * @returns The result of applying the update. */ abstract applyUpdate( args: Field.UpdateArgs ): Field.UpdateResult; /** * Apply a system patch to the field. * * @param args - The arguments for the patch. * * @returns The result of applying the patch. */ abstract applyPatch( args: Field.PatchArgs ): Field.PatchResult; /** * Unapply a system patch to the field. * * @param args - The arguments for the patch. * * @returns The result of unapplying the patch. */ abstract unapplyPatch( args: Field.PatchArgs ): Field.PatchResult; /** * Merge two change objects into a single change object. * * @param first - The first change object of interest. * * @param second - The second change object of interest. * * @returns A new change object which represents both changes. */ abstract mergeChange(first: Change, second: Change): Change; /** * Merge two patch objects into a single patch object. * * @param first - The first patch object of interest. * * @param second - The second patch object of interest. * * @returns A new patch object which represents both patches. */ abstract mergePatch(first: Patch, second: Patch): Patch; } /** * The namespace for the `Field` class statics. */ export namespace Field { /** * An options object for initializing a field. */ export interface IOptions { /** * The human-readable description of the field. * * The default is `''`. */ description?: string; } /** * A type alias for the arguments to an update operation. */ export type UpdateArgs = { /** * The previous value of the field. */ readonly previous: Value; /** * The user update for the field. */ readonly update: Update; /** * The metadata for the field. */ readonly metadata: Metadata; /** * The datastore version. */ readonly version: number; /** * The datastore id. */ readonly storeId: number; }; /** * A type alias for the result of an update operation. */ export type UpdateResult = { /** * The new value of the field. */ readonly value: Value; /** * The user-facing change for the field. */ readonly change: Change; /** * The system-facing patch for the field. */ readonly patch: Patch; }; /** * A type alias for the arguments to a patch operation. */ export type PatchArgs = { /** * The previous value of the field. */ readonly previous: Value; /** * The system patch for the field. */ readonly patch: Patch; /** * The metadata for the field. */ readonly metadata: Metadata; }; /** * A type alias for the result of a patch operation. */ export type PatchResult = { /** * The new value of the field. */ readonly value: Value; /** * The user-facing change for the field. */ readonly change: Change; }; } /** * A type alias which is compatible with any field type. */ export type AnyField = Field< ReadonlyJSONValue, ReadonlyJSONValue, ReadonlyJSONValue, ReadonlyJSONValue, ReadonlyJSONValue >; lumino-2021.12.13/packages/datastore/src/fields.ts000066400000000000000000000062001415564225700215700ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2018, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { ReadonlyJSONValue } from '@lumino/coreutils'; import { ListField } from './listfield'; import { MapField } from './mapfield'; import { RegisterField } from './registerfield'; import { TextField } from './textfield'; /** * The namespace for the `Fields` factory functions. */ export namespace Fields { /** * A factory function which creates a boolean register field. * * @param options - The options for the field. The default `value` * option is `false`. * * @returns A new boolean register field. */ export function Boolean( options: Partial> = {} ): RegisterField { return new RegisterField({ value: false, ...options }); } /** * A factory function which creates a number register field. * * @param options - The options for the field. The default `value` * option is `0`. * * @returns A new number register field. */ export function Number( options: Partial> = {} ): RegisterField { return new RegisterField({ value: 0, ...options }); } /** * A factory function which creates a string register field. * * @param options - The options for the field. The default `value` * option is `''`. * * @returns A new string register field. */ export function String( options: Partial> = {} ): RegisterField { return new RegisterField({ value: '', ...options }); } /** * A factory function which creates a list field. * * @param options - The options for the field. * * @returns A new list field. */ export function List( options: ListField.IOptions = {} ): ListField { return new ListField(options); } /** * A factory function which creates a map field. * * @param options - The options for the field. * * @returns A new map field. */ export function Map( options: MapField.IOptions = {} ): MapField { return new MapField(options); } /** * A factory function which creates a register field. * * @param options - The options for the field. * * @returns A new register field. */ export function Register( options: RegisterField.IOptions ): RegisterField { return new RegisterField(options); } /** * A factory function which creates a text field. * * @param options - The options for the field. * * @returns A new text field. */ export function Text(options: TextField.IOptions = {}): TextField { return new TextField(options); } } lumino-2021.12.13/packages/datastore/src/index.ts000066400000000000000000000013711415564225700214350ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2018, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ export * from './datastore'; export * from './field'; export * from './fields'; export * from './listfield'; export * from './mapfield'; export * from './record'; export * from './registerfield'; export * from './schema'; export * from './serveradapter'; export * from './table'; export * from './textfield'; lumino-2021.12.13/packages/datastore/src/listfield.ts000066400000000000000000000460071415564225700223120ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2018, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { ArrayExt, StringExt } from '@lumino/algorithm'; import { ReadonlyJSONValue } from '@lumino/coreutils'; import { Field } from './field'; import { createTriplexIds } from './utilities'; /** * A field which represents a collaborative list of values. */ export class ListField extends Field< ListField.Value, ListField.Update, ListField.Metadata, ListField.Change, ListField.Patch > { /** * Construct a new list field. * * @param options - The options for initializing the field. */ constructor(options: ListField.IOptions = {}) { super(options); } /** * The discriminated type of the field. */ get type(): 'list' { return 'list'; } /** * Create the initial value for the field. * * @returns The initial value for the field. */ createValue(): ListField.Value { return []; } /** * Create the metadata for the field. * * @returns The metadata for the field. */ createMetadata(): ListField.Metadata { return { ids: [], cemetery: {} }; } /** * Apply a user update to the field. * * @param args - The arguments for the update. * * @returns The result of applying the update. */ applyUpdate( args: Field.UpdateArgs< ListField.Value, ListField.Update, ListField.Metadata > ): Field.UpdateResult< ListField.Value, ListField.Change, ListField.Patch > { // Unpack the arguments. let { previous, update, metadata, version, storeId } = args; // Create a clone of the previous value. let clone = [...previous]; // Set up the change and patch arrays. let change: ListField.ChangePart[] = []; let patch: ListField.PatchPart[] = []; // Coerce the update into an array of splices. if (Private.isSplice(update)) { update = [update]; } // Iterate over the update. for (let splice of update) { // Apply the splice to the clone. let obj = Private.applySplice(clone, splice, metadata, version, storeId); // Update the change array. change.push(obj.change); // Update the patch array. patch.push(obj.patch); } // Return the update result. return { value: clone, change, patch }; } /** * Apply a system patch to the field. * * @param args - The arguments for the patch. * * @returns The result of applying the patch. */ applyPatch( args: Field.PatchArgs< ListField.Value, ListField.Patch, ListField.Metadata > ): Field.PatchResult, ListField.Change> { // Unpack the arguments. let { previous, patch, metadata } = args; // Create a clone of the previous value. let clone = [...previous]; // Set up the change array. let change: ListField.ChangePart[] = []; // Iterate over the patch. for (let part of patch) { // Apply the patch part to the value. let result = Private.applyPatch(clone, part, metadata); // Update the change array. change.push(...result); } // Return the patch result. return { value: clone, change }; } /** * Unapply a system patch to the field. * * @param args - The arguments for the patch. * * @returns The result of unapplying the patch. */ unapplyPatch( args: Field.PatchArgs< ListField.Value, ListField.Patch, ListField.Metadata > ): Field.PatchResult, ListField.Change> { // Unpack the arguments. let { previous, patch, metadata } = args; // Create a clone of the previous value. let clone = [...previous]; // Set up the change array. let change: ListField.ChangePart[] = []; // Iterate over the patch. for (let part of patch) { let reversed = { removedIds: part.insertedIds, insertedIds: part.removedIds, removedValues: part.insertedValues, insertedValues: part.removedValues }; // Apply the patch part to the value. let result = Private.applyPatch(clone, reversed, metadata); // Update the change array. change.push(...result); } // Return the patch result. return { value: clone, change }; } /** * Merge two change objects into a single change object. * * @param first - The first change object of interest. * * @param second - The second change object of interest. * * @returns A new change object which represents both changes. */ mergeChange( first: ListField.Change, second: ListField.Change ): ListField.Change { return [...first, ...second]; } /** * Merge two patch objects into a single patch object. * * @param first - The first patch object of interest. * * @param second - The second patch object of interest. * * @returns A new patch object which represents both patches. */ mergePatch( first: ListField.Patch, second: ListField.Patch ): ListField.Patch { return [...first, ...second]; } } /** * The namespace for the `ListField` class statics. */ export namespace ListField { /** * An options object for initializing a list field. */ export interface IOptions extends Field.IOptions {} /** * A type alias for the list field value type. */ export type Value = ReadonlyArray; /** * A type alias for a list field splice. */ export type Splice = { /** * The index of the splice. */ readonly index: number; /** * The number of values to remove. */ readonly remove: number; /** * The values to insert. */ readonly values: ReadonlyArray; }; /** * A type alias for the list field update type. */ export type Update = | Splice | ReadonlyArray>; /** * A type alias for the list field metadata type. */ export type Metadata = { /** * An array of ids corresponding to the list elements. */ readonly ids: Array; /** * The cemetery for concurrently deleted elements. */ readonly cemetery: { [id: string]: number }; }; /** * A type alias for a list field change part. */ export type ChangePart = { /** * The index of the modification. */ readonly index: number; /** * The values that were removed. */ readonly removed: ReadonlyArray; /** * The values that were inserted. */ readonly inserted: ReadonlyArray; }; /** * A type alias for the list field change type. */ export type Change = ReadonlyArray< ChangePart >; /** * A type alias for the list field patch part. */ export type PatchPart = { /** * The ids that were removed. */ readonly removedIds: ReadonlyArray; /** * The values that were removed. */ readonly removedValues: ReadonlyArray; /** * The ids that were inserted. */ readonly insertedIds: ReadonlyArray; /** * The values that were inserted. */ readonly insertedValues: ReadonlyArray; }; /** * A type alias for the list field patch type. */ export type Patch = ReadonlyArray>; } /** * The namespace for the module implementation details. */ namespace Private { /** * A type-guard function for a list field update type. */ export function isSplice( value: ListField.Update ): value is ListField.Splice { return !Array.isArray(value); } /** * A type alias for the result of a splice operation. */ export type SpliceResult = { /** * The user-facing change part for the splice. */ readonly change: ListField.ChangePart; /** * The system-facing patch part for the splice. */ readonly patch: ListField.PatchPart; }; /** * Apply a splice to a list field. * * @param array - The mutable current value of the field. * * @param splice - The splice to apply to the field. * * @param metadata - The metadata for the field. * * @param version - The current datastore version. * * @param storeId - The unique id of the datastore. * * @returns The result of the splice operation. */ export function applySplice( array: T[], splice: ListField.Splice, metadata: ListField.Metadata, version: number, storeId: number ): SpliceResult { // Unpack the splice. let { index, remove, values } = splice; // Clamp the index to the array bounds. if (index < 0) { index = Math.max(0, index + array.length); } else { index = Math.min(index, array.length); } // Clamp the remove count to the array bounds. let count = Math.min(remove, array.length - index); // Fetch the lower and upper identifiers. let lower = index === 0 ? '' : metadata.ids[index - 1]; let upper = index === array.length ? '' : metadata.ids[index]; // Create the ids for the splice. let ids = createTriplexIds(values.length, version, storeId, lower, upper); // Apply the splice to the ids and values. let removedIds = spliceArray(metadata.ids, index, count, ids); let removedValues = spliceArray(array, index, count, values); // Create the change object. let change = { index, removed: removedValues, inserted: values }; // Create the patch object. let patch = { removedIds, removedValues, insertedIds: ids, insertedValues: values }; // Return the splice result. return { change, patch }; } /** * Apply a patch to a list field. * * @param value - The mutable current value of the field. * * @param patch - The patch part to apply to the field. * * @param metadata - The metadata for the field. * * @returns The user-facing change array for the patch. */ export function applyPatch( value: T[], patch: ListField.PatchPart, metadata: ListField.Metadata ): ListField.Change { // Unpack the patch. let { removedIds, insertedIds, insertedValues } = patch; // Set up the change array. let change: ListField.ChangePart[] = []; // Process the removed identifiers, if necessary. if (removedIds.length > 0) { // Chunkify the removed identifiers, // or increment the removed ids in the cemetery. let chunks = findRemovedChunks(removedIds, metadata); // Process the chunks. while (chunks.length > 0) { // Pop the last-most chunk. let { index, count } = chunks.pop()!; // Remove the identifiers from the metadata. metadata.ids.splice(index, count); // Remove the values from the array. let removed = value.splice(index, count); // Add the change part to the change array. change.push({ index, removed, inserted: [] }); } } // Process the inserted identifiers, if necessary. if (insertedIds.length > 0) { // Chunkify the inserted identifiers, or decrement the removed // ids in the cemetery. let chunks = findInsertedChunks(insertedIds, insertedValues, metadata); // Process the chunks. while (chunks.length > 0) { // Pop the last-most chunk. let { index, ids, values } = chunks.pop()!; // Insert the identifiers into the metadata. spliceArray(metadata.ids, index, 0, ids); // Insert the values into the array. spliceArray(value, index, 0, values); // Add the change part to the change array. change.push({ index, removed: [], inserted: values }); } } // Return the change array. return change; } /** * A type alias for a remove chunk. */ type RemoveChunk = { // The index of the removal. index: number; // The number of elements to remove. count: number; }; /** * Convert an array of identifiers into removal chunks. * * @param ids - The ids to remove from the metadta. * * @param metadata - The metadata for the list field. * * @returns The ordered chunks to remove. * * #### Notes * The metadata may be mutated if concurrently removed chunks are encountered. */ function findRemovedChunks( ids: ReadonlyArray, metadata: ListField.Metadata ): RemoveChunk[] { // Set up the chunks array. let chunks: RemoveChunk[] = []; // Set up the iteration index. let i = 0; // Fetch the identifier array length. let n = ids.length; // Iterate over the identifiers to remove. while (i < n) { // Find the boundary identifier for the current id. let j = ArrayExt.lowerBound(metadata.ids, ids[i], StringExt.cmp); // If the boundary is at the end of the array, or if the boundary id // does not match the id we are looking for, then we are dealing with // a concurrently deleted value. In that case, increment its reference // in the cemetery and continue processing ids. if (j === metadata.ids.length || metadata.ids[j] !== ids[i]) { let count = metadata.cemetery[ids[i]] || 0; metadata.cemetery[ids[i]] = count + 1; i++; continue; } // Set up the chunk index. let index = j; // Set up the chunk count. let count = 0; // Find the extent of the chunk. while (i < n && StringExt.cmp(ids[i], metadata.ids[j]) === 0) { count++; i++; j++; } // Add the chunk to the chunks array, or bump the id index. if (count > 0) { chunks.push({ index, count }); } else { i++; } } // Return the computed chunks. return chunks; } /** * A type alias for an insert chunk. */ type InsertChunk = { // The index of the insert. index: number; // The identifiers to insert. ids: string[]; // The values to insert. values: T[]; }; /** * Convert arrays of identifiers and values into insert chunks. * * @param ids - The ids to be inserted. * * @param values - The values to be inserted. * * @param metadata - The metadata for the list field. * * @returns The ordered chunks to insert. * * #### Notes * The metadata may be mutated if concurrently removed chunks are encountered. */ function findInsertedChunks( ids: ReadonlyArray, values: ReadonlyArray, metadata: ListField.Metadata ): InsertChunk[] { let indices: number[] = []; let insertIds: string[] = []; let insertValues: T[] = []; for (let i = 0; i < ids.length; i++) { // Check if the id has been concurrently deleted. If so, update // the cemetery, and continue processing without inserting the id. if (checkCemeteryForInsert(ids[i], metadata.cemetery)) { continue; } // Add the id to the ids which will be actually inserted. insertIds.push(ids[i]); indices.push(ArrayExt.lowerBound(metadata.ids, ids[i], StringExt.cmp)); insertValues.push(values[i]); } return chunkifyInsertions(insertIds, insertValues, indices); } /** * Consolidate inserted IDs into a set of chunks so that we can splice them * into the existing value with a minimal number of splices. * * @param ids - The ids to be inserted. * * @param values - The values to be inserted. Should be the same length as ids. * * @param indices - The indices at which to insert the text. Should be the same length as ids. * * @returns The ordered chunks to insert. */ function chunkifyInsertions( ids: ReadonlyArray, values: ReadonlyArray, indices: ReadonlyArray ): InsertChunk[] { // Set up the chunks array. let chunks: InsertChunk[] = []; // Set up the loop over the ids to insert. let insertIndex: number; let i = 0; while (i < ids.length) { // Reset the insert chunk data let chunkIds: string[] = []; let chunkValues: T[] = []; insertIndex = indices[i]; // Find the extent of the chunk while (indices[i] === insertIndex && i < ids.length) { chunkIds.push(ids[i]); chunkValues.push(values[i]); i++; } if (chunkValues.length) { chunks.push({ index: insertIndex, ids: chunkIds, values: chunkValues }); } } return chunks; } /** * Check if an id should be inserted, or if it has been concurrently deleted. * * @param id - the id to check. * * @param cemetery - the cemetery which determines whether the id should be inserted. * * @returns whether the id was found, indicating that it shouldn't be inserted. * * #### Notes * If the ID *is* found in the cemetery, its value in the cemetery is decremented, * reflecting that it is closer to being shown. */ function checkCemeteryForInsert( id: string, cemetery: { [x: string]: number } ): boolean { let count = cemetery[id] || 0; if (count === 1) { delete cemetery[id]; return true; } if (count > 1) { cemetery[id] = count - 1; return true; } return false; } /** * Splice data into an array. * * #### Notes * This is intentionally similar to Array.splice, but chunks the splices into * multiple splices so that it does not crash if the number of spliced IDs * is greater than the maximum number of arguments for a function. * * @param arr - the array on which to perform the splice. * * @param start - the start index for the splice. * * @param deleteCount - how many indices to remove. * * @param items - the items to splice into the array. * * @returns an array of the deleted elements. */ function spliceArray( arr: T[], start: number, deleteCount?: number, items?: ReadonlyArray ): ReadonlyArray { if (!items) { return arr.splice(start, deleteCount); } let size = 100000; if (items.length < size) { return arr.splice(start, deleteCount || 0, ...items); } let deleted = arr.splice(start, deleteCount); let n = Math.floor(items.length / size); let idx = 0; for (let i = 0; i < n; i++, idx += size) { arr.splice(start + idx, 0, ...items.slice(idx, idx + size)); } arr.splice(start + idx, 0, ...items.slice(idx)); return deleted; } } lumino-2021.12.13/packages/datastore/src/mapfield.ts000066400000000000000000000247471415564225700221230ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2018, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { ArrayExt, StringExt } from '@lumino/algorithm'; import { ReadonlyJSONValue } from '@lumino/coreutils'; import { Field } from './field'; import { createDuplexId } from './utilities'; /** * A field which represents a collaborative key:value map. */ export class MapField extends Field< MapField.Value, MapField.Update, MapField.Metadata, MapField.Change, MapField.Patch > { /** * Construct a new map field. * * @param options - The options for initializing the field. */ constructor(options: MapField.IOptions = {}) { super(options); } /** * The discriminated type of the field. */ get type(): 'map' { return 'map'; } /** * Create the initial value for the field. * * @returns The initial value for the field. */ createValue(): MapField.Value { return {}; } /** * Create the metadata for the field. * * @returns The metadata for the field. */ createMetadata(): MapField.Metadata { return { ids: {}, values: {} }; } /** * Apply a user update to the field. * * @param args - The arguments for the update. * * @returns The result of applying the update. */ applyUpdate( args: Field.UpdateArgs< MapField.Value, MapField.Update, MapField.Metadata > ): Field.UpdateResult< MapField.Value, MapField.Change, MapField.Patch > { // Unpack the arguments. let { previous, update, metadata, version, storeId } = args; // Create the id for the values. let id = createDuplexId(version, storeId); // Create a clone of the previous value. let clone = { ...previous }; // Set up the previous and current change parts. let prev: { [key: string]: T | null } = {}; let curr: { [key: string]: T | null } = {}; // Iterate over the update. for (let key in update) { // Insert the update value into the metadata. let value = Private.insertIntoMetadata(metadata, key, id, update[key]); // Update the clone with the new value. if (value === null) { delete clone[key]; } else { clone[key] = value; } // Update the previous change part. prev[key] = key in previous ? previous[key] : null; // Update the current change part. curr[key] = value; } // Create the change object. let change = { previous: prev, current: curr }; // Create the patch object. let patch = { id, values: update }; // Return the update result. return { value: clone, change, patch }; } /** * Apply a system patch to the field. * * @param args - The arguments for the patch. * * @returns The result of applying the patch. */ applyPatch( args: Field.PatchArgs< MapField.Value, MapField.Patch, MapField.Metadata > ): Field.PatchResult, MapField.Change> { // Unpack the arguments. let { previous, patch, metadata } = args; // Unpack the patch. let { id, values } = patch; // Create a clone of the previous value. let clone = { ...previous }; // Set up the previous and current change parts. let prev: { [key: string]: T | null } = {}; let curr: { [key: string]: T | null } = {}; // Iterate over the values. for (let key in values) { // Insert the patch value into the metadata. let value = Private.insertIntoMetadata(metadata, key, id, values[key]); // Update the clone with the new value. if (value === null) { delete clone[key]; } else { clone[key] = value; } // Update the previous change part. prev[key] = key in previous ? previous[key] : null; // Update the current change part. curr[key] = value; } // Create the change object. let change = { previous: prev, current: curr }; // Return the patch result. return { value: clone, change }; } /** * Apply a system patch to the field. * * @param args - The arguments for the patch. * * @returns The result of applying the patch. */ unapplyPatch( args: Field.PatchArgs< MapField.Value, MapField.Patch, MapField.Metadata > ): Field.PatchResult, MapField.Change> { // Unpack the arguments. let { previous, patch, metadata } = args; // Unpack the patch. let { id, values } = patch; // Create a clone of the previous value. let clone = { ...previous }; // Set up the previous and current change parts. let prev: { [key: string]: T | null } = {}; let curr: { [key: string]: T | null } = {}; // Iterate over the values. for (let key in values) { // Remove the patch value from the metadata. let value = Private.removeFromMetadata(metadata, key, id); // Update the clone with the new value. if (value === null) { delete clone[key]; } else { clone[key] = value; } // Update the previous change part. prev[key] = key in previous ? previous[key] : null; // Update the current change part. curr[key] = value; } // Create the change object. let change = { previous: prev, current: curr }; // Return the patch result. return { value: clone, change }; } /** * Merge two change objects into a single change object. * * @param first - The first change object of interest. * * @param second - The second change object of interest. * * @returns A new change object which represents both changes. */ mergeChange( first: MapField.Change, second: MapField.Change ): MapField.Change { let previous = { ...second.previous, ...first.previous }; let current = { ...first.current, ...second.current }; return { previous, current }; } /** * Merge two patch objects into a single patch object. * * @param first - The first patch object of interest. * * @param second - The second patch object of interest. * * @returns A new patch object which represents both patches. */ mergePatch( first: MapField.Patch, second: MapField.Patch ): MapField.Patch { return { id: second.id, values: { ...first.values, ...second.values } }; } } /** * The namespace for the `MapField` class statics. */ export namespace MapField { /** * An options object for initializing a map field. */ export interface IOptions extends Field.IOptions {} /** * A type alias for the map field value type. */ export type Value = { readonly [key: string]: T; }; /** * A type alias for the map field update type. */ export type Update = { readonly [key: string]: T | null; }; /** * A type alias for the map field metadata type. */ export type Metadata = { /** * A mapping of key:id-history. */ readonly ids: { [key: string]: Array }; /** * A mapping of key:value-history. */ readonly values: { [key: string]: Array }; }; /** * A type alias for the map field change type. */ export type Change = { /** * The previous values of the changed items. */ readonly previous: { readonly [key: string]: T | null }; /** * The current values of the changed items. */ readonly current: { readonly [key: string]: T | null }; }; /** * A type alias for the map field patch type. */ export type Patch = { /** * The unique id associated with the values. */ readonly id: string; /** * The current values of the changed items. */ readonly values: { readonly [key: string]: T | null }; }; } /** * The namespace for the module implementation details. */ namespace Private { /** * Insert a value into the map field metadata. * * @param metadata - The metadata of interest. * * @param key - The key of interest. * * @param id - The unique id for the value. * * @param value - The value to insert. * * @returns The current value for the key. * * #### Notes * If the id already exists, the old value will be overwritten. */ export function insertIntoMetadata( metadata: MapField.Metadata, key: string, id: string, value: T | null ): T | null { // Fetch the id and value arrays for the given key. let ids = metadata.ids[key] || (metadata.ids[key] = []); let values = metadata.values[key] || (metadata.values[key] = []); // Find the insert index for the id. let i = ArrayExt.lowerBound(ids, id, StringExt.cmp); // Overwrite or insert the value as appropriate. if (i < ids.length && ids[i] === id) { values[i] = value; } else { ArrayExt.insert(ids, i, id); ArrayExt.insert(values, i, value); } // Return the current value for the key. return values[values.length - 1]; } /** * Remove a value from the map field metadata. * * @param metadata - The metadata of interest. * * @param key - The key of interest. * * @param id - The unique id for the value. * * @returns The current value for the key, or null if there is no value. * * #### Notes * If the id is not in the metadata, this is a no-op. */ export function removeFromMetadata( metadata: MapField.Metadata, key: string, id: string ): T | null { // Fetch the id and value arrays for the given key. let ids = metadata.ids[key] || (metadata.ids[key] = []); let values = metadata.values[key] || (metadata.values[key] = []); // Find the insert index for the id. let i = ArrayExt.lowerBound(ids, id, StringExt.cmp); // Find and remove the index for the id. if (ids[i] === id) { ArrayExt.removeAt(ids, i); ArrayExt.removeAt(values, i); } // Return the current value for the key. return values.length ? values[values.length - 1] : null; } } lumino-2021.12.13/packages/datastore/src/record.ts000066400000000000000000000043711415564225700216070ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2018, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { Schema } from './schema'; /** * A type alias for a datastore record. */ export type Record = Record.Base & Record.Value; /** * The namespace for the `Record` type statics. */ export namespace Record { /** * A type alias for the record base type. */ export type Base = { /** * The unique id of the record. */ readonly $id: string; /** * @internal * * The metadata for the record. */ readonly '@@metadata': Metadata; }; /** * A type alias for the record value type. */ export type Value = { readonly [N in keyof S['fields']]: S['fields'][N]['ValueType']; }; /** * A type alias for the record update type. */ export type Update = { readonly [N in keyof S['fields']]?: S['fields'][N]['UpdateType']; }; /** * A type alias for the record change type. */ export type Change = { readonly [N in keyof S['fields']]?: S['fields'][N]['ChangeType']; }; /** * A type alias for the record patch type. */ export type Patch = { readonly [N in keyof S['fields']]?: S['fields'][N]['PatchType']; }; /** * @internal * * A type alias for the record metadata type. */ export type Metadata = { readonly [N in keyof S['fields']]: S['fields'][N]['MetadataType']; }; /** * @internal * * A type alias for the record mutable change type. */ export type MutableChange = { [N in keyof S['fields']]?: S['fields'][N]['ChangeType']; }; /** * @internal * * A type alias for the record mutable patch type. */ export type MutablePatch = { [N in keyof S['fields']]?: S['fields'][N]['PatchType']; }; } lumino-2021.12.13/packages/datastore/src/registerfield.ts000066400000000000000000000204111415564225700231520ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2018, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { ArrayExt, StringExt } from '@lumino/algorithm'; import { ReadonlyJSONValue } from '@lumino/coreutils'; import { Field } from './field'; import { createDuplexId } from './utilities'; /** * A field which represents a collaborative atomic value. */ export class RegisterField extends Field< RegisterField.Value, RegisterField.Update, RegisterField.Metadata, RegisterField.Change, RegisterField.Patch > { /** * Construct a new register field. * * @param options - The options for initializing the field. */ constructor(options: RegisterField.IOptions) { super(options); this.value = options.value; } /** * The discriminated type of the field. */ get type(): 'register' { return 'register'; } /** * The initial value for the field. */ readonly value: T; /** * Create the initial value for the field. * * @returns The initial value for the field. */ createValue(): RegisterField.Value { return this.value; } /** * Create the metadata for the field. * * @returns The metadata for the field. */ createMetadata(): RegisterField.Metadata { return { ids: [], values: [] }; } /** * Apply a user update to the field. * * @param args - The arguments for the update. * * @returns The result of applying the update. */ applyUpdate( args: Field.UpdateArgs< RegisterField.Value, RegisterField.Update, RegisterField.Metadata > ): Field.UpdateResult< RegisterField.Value, RegisterField.Change, RegisterField.Patch > { // Unpack the arguments. let { previous, update, metadata, version, storeId } = args; // Create the id for the value. let id = createDuplexId(version, storeId); // Insert the update value into the metadata. let value = Private.insertIntoMetadata(metadata, id, update); // Create the change object. let change = { previous, current: value }; // Create the patch object. let patch = { id, value: update }; // Return the result of the update. return { value, change, patch }; } /** * Apply a system patch to the field. * * @param args - The arguments for the patch. * * @returns The result of applying the patch. */ applyPatch( args: Field.PatchArgs< RegisterField.Value, RegisterField.Patch, RegisterField.Metadata > ): Field.PatchResult, RegisterField.Change> { // Unpack the arguments. let { previous, patch, metadata } = args; // Insert the patch value into the metadata. let value = Private.insertIntoMetadata(metadata, patch.id, patch.value); // Create the change object. let change = { previous, current: value }; // Return the result of the patch. return { value, change }; } /** * Unapply a system patch to the field. * * @param args - The arguments for the patch. * * @returns The result of unapplying the patch. */ unapplyPatch( args: Field.PatchArgs< RegisterField.Value, RegisterField.Patch, RegisterField.Metadata > ): Field.PatchResult, RegisterField.Change> { // Unpack the arguments. let { previous, patch, metadata } = args; // Remove the patch value from the metadata. let value = Private.removeFromMetadata(metadata, patch.id, this.value); // Create the change object. let change = { previous, current: value }; // Return the result of the patch. return { value, change }; } /** * Merge two change objects into a single change object. * * @param first - The first change object of interest. * * @param second - The second change object of interest. * * @returns A new change object which represents both changes. */ mergeChange( first: RegisterField.Change, second: RegisterField.Change ): RegisterField.Change { return { previous: first.previous, current: second.current }; } /** * Merge two patch objects into a single patch object. * * @param first - The first patch object of interest. * * @param second - The second patch object of interest. * * @returns A new patch object which represents both patches. */ mergePatch( first: RegisterField.Patch, second: RegisterField.Patch ): RegisterField.Patch { return second; } } /** * The namespace for the `RegisterField` class statics. */ export namespace RegisterField { /** * An options object for initializing a register field. */ export interface IOptions extends Field.IOptions { /** * The initial value for the field. */ value: T; } /** * A type alias for the register field value type. */ export type Value = T; /** * A type alias for the register field update type. */ export type Update = T; /** * A type alias for the register field change type. */ export type Change = { /** * The previous value of the field. */ readonly previous: T; /** * The current value of the field. */ readonly current: T; }; /** * A type alias for the register field patch type. */ export type Patch = { /** * The unique id for the value. */ readonly id: string; /** * The current value of the field. */ readonly value: T; }; /** * A type alias for the register field metadata type. */ export type Metadata = { /** * An array of id history. */ readonly ids: Array; /** * An array of value history. */ readonly values: Array; }; } /** * The namespace for the module implementation details. */ namespace Private { /** * Insert a value into the register field metadata. * * @param metadata - The metadata of interest. * * @param id - The unique id for the value. * * @param value - The value to insert. * * @returns The current value for the register field. * * #### Notes * If the id already exists, the old value will be overwritten. */ export function insertIntoMetadata( metadata: RegisterField.Metadata, id: string, value: T ): T { // Unpack the metadata. let { ids, values } = metadata; // Find the insert index for the id. let i = ArrayExt.lowerBound(ids, id, StringExt.cmp); // Overwrite or insert the value as appropriate. if (i < ids.length && ids[i] === id) { values[i] = value; } else { ArrayExt.insert(ids, i, id); ArrayExt.insert(values, i, value); } // Return the current value for the register field. return values[values.length - 1]; } /** * Remove a value from the register field metadata. * * @param metadata - The metadata of interest. * * @param id - The unique id for the value. * * @param initial - The default value for the field * * @returns The current value for the register field. * * #### Notes * If the id does not exist in the metadata, this is a no-op. */ export function removeFromMetadata( metadata: RegisterField.Metadata, id: string, initial: T ): T { // Unpack the metadata. let { ids, values } = metadata; // Find the remove index for the id. let i = ArrayExt.lowerBound(ids, id, StringExt.cmp); if (ids[i] === id) { ArrayExt.removeAt(ids, i); ArrayExt.removeAt(values, i); } // Return the current value for the register field. // If there are no values in the metadata, return the default value. return values.length ? values[values.length - 1] : initial; } } lumino-2021.12.13/packages/datastore/src/schema.ts000066400000000000000000000027611415564225700215720ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2018, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { AnyField } from './field'; /** * A type definition for a table schema. * * #### Notes * The datastore assumes that peers may safely collaborate on tables * which share the same schema `id`. * * The schema `id` must be changed whenever changes are made to the * schema, or undefined behavior will result. */ export type Schema = { /** * The unique identifier for the schema. */ readonly id: string; /** * The field definitions for the schema. * * #### Notes * Field names cannot begin with `$` or `@`. */ readonly fields: { readonly [name: string]: AnyField }; }; const invalidFieldnameLeads = ['$', '@']; /** * Validate a schema definition. */ export function validateSchema(schema: Schema): string[] { const errors = []; // Ensure that field names do not begin with `$` or `@`. for (let name in schema.fields) { if (invalidFieldnameLeads.indexOf(name[0]) !== -1) { errors.push( `Invalid field name: '${name}'. Cannot start field name with '${name[0]}'` ); } } return errors; } lumino-2021.12.13/packages/datastore/src/serveradapter.ts000066400000000000000000000041561415564225700232010ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2019, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { IDisposable } from '@lumino/disposable'; import { Datastore } from './datastore'; /** * An interface for a bridge between a datastore and a patch server. * Patch servers should expose an interface like this one, which mediates * outgoing patches from a given store, and delivers incoming patches to a * store. */ export interface IServerAdapter extends IDisposable { /** * Broadcast a transaction from a datastore to collaborators. * * @param transaction - the transaction to broadcast to collaborators. * * #### Notes * This is expected to be called by a datastore, and not by any other * user. Direct invocations of this function may have unexpected results. */ broadcast(transaction: Datastore.Transaction): void; /** * Undo a transaction by id. This sends an undo message to the patch server, * but the undo is not actually done until the datastore recieves the * corresponding transaction and applies it. * * @param id: the transaction to undo. */ undo(id: string): Promise; /** * Redo a transaction by id. * * @param id: the transaction to redo. */ redo(id: string): Promise; /** * A callback to be invoked when a remote transaction is received by the * server adapter. */ onRemoteTransaction: ((transaction: Datastore.Transaction) => void) | null; /** * A callback to be invoked when an undo message is received by the server * adapter. */ onUndo: ((transaction: Datastore.Transaction) => void) | null; /** * A callback to be invoked when an redo message is received by the server * adapter. */ onRedo: ((transaction: Datastore.Transaction) => void) | null; } lumino-2021.12.13/packages/datastore/src/table.ts000066400000000000000000000326371415564225700214260ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2018, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { IIterable, IIterator, IterableOrArrayLike, StringExt } from '@lumino/algorithm'; import { BPlusTree } from '@lumino/collections'; import { Datastore } from './datastore'; import { Record } from './record'; import { Schema } from './schema'; /** * A datastore object which holds a collection of records. */ export class Table implements IIterable> { /** * @internal * * Create a new datastore table. * * @param schema - The schema for the table. * * @param context - The datastore context. * * @returns A new datastore table. */ static create( schema: U, context: Datastore.Context ): Table { return new Table(schema, context); } /** * @internal * * Create a new datastore table with a previously exported state. * * @param schema - The schema for the table. * * @param context - The datastore context. * * @returns A new datastore table. */ static recreate( schema: U, context: Datastore.Context, records: IterableOrArrayLike> ): Table { return new Table(schema, context, records); } /** * @internal * * Apply a patch to a datastore table. * * @param table - The table of interest. * * @param data - The patch to apply to the table. * * @returns The user-facing change to the table. */ static patch( table: Table, data: Table.Patch ): Table.Change { // Create the change object. let tc: Table.MutableChange = {}; // Fetch common variables. let schema = table.schema; let records = table._records; let cmp = Private.recordIdCmp; // Iterate over the dataset. for (let id in data) { // Get or create the old record. let old = records.get(id, cmp) || Private.createRecord(schema, id); // Apply the patch and create the new record. let { record, change } = Private.applyPatch(schema, old, data[id]); // Replace the old record in the table. records.insert(record); // Update the change object. tc[id] = change; } // Return the change object. return tc; } /** * @internal * * Unapply a patch to a datastore table, thereby undoing that patch. * * @param table - The table of interest. * * @param data - The patch to apply to the table. * * @returns The user-facing change to the table. */ static unpatch( table: Table, data: Table.Patch ): Table.Change { // Create the change object. let tc: Table.MutableChange = {}; // Fetch common variables. let schema = table.schema; let records = table._records; let cmp = Private.recordIdCmp; // Iterate over the dataset. for (let id in data) { // Get or create the old record. let old = records.get(id, cmp) || Private.createRecord(schema, id); // Apply the patch and create the new record. let { record, change } = Private.unapplyPatch(schema, old, data[id]); // Replace the old record in the table. records.insert(record); // Update the change object. tc[id] = change; } // Return the change object. return tc; } /** * The schema for the table. * * #### Complexity * `O(1)` */ readonly schema: S; /** * Whether the table is empty. * * #### Complexity * `O(1)` */ get isEmpty(): boolean { return this._records.isEmpty; } /** * The size of the table. * * #### Complexity * `O(1)` */ get size(): number { return this._records.size; } /** * Create an iterator over the records in the table. * * @returns A new iterator over the table records. * * #### Complexity * `O(log32 n)` */ iter(): IIterator> { return this._records.iter(); } /** * Test whether the table has a particular record. * * @param id - The id of the record of interest. * * @returns `true` if the table has the record, `false` otherwise. * * #### Complexity * `O(log32 n)` */ has(id: string): boolean { return this._records.has(id, Private.recordIdCmp); } /** * Get the record for a particular id in the table. * * @param id - The id of the record of interest. * * @returns The record for the specified id, or `undefined` if no * such record exists. * * #### Complexity * `O(log32 n)` */ get(id: string): Record | undefined { return this._records.get(id, Private.recordIdCmp); } /** * Update one or more records in the table. * * @param data - The data for updating the records. * * #### Notes * If a specified record does not exist, it will be created. * * This method may only be called during a datastore transaction. */ update(data: Table.Update): void { // Fetch the context. let context = this._context; // Ensure the update happens during a transaction. if (!context.inTransaction) { throw new Error('A table can only be updated during a transaction.'); } // Fetch common variables. let schema = this.schema; let records = this._records; let cmp = Private.recordIdCmp; // Iterate over the data. for (let id in data) { // Get or create the old record. let old = records.get(id, cmp) || Private.createRecord(schema, id); // Apply the update and create the new record. let record = Private.applyUpdate(schema, old, data[id], context); // Replace the old record in the table. records.insert(record); } } /** * Construct a new datastore table. * * @param schema - The schema for the table. * * @param context - The datastore context. */ private constructor( schema: S, context: Datastore.Context, records?: IterableOrArrayLike> ) { this.schema = schema; this._context = context; if (records) { this._records.assign(records); } } private _context: Datastore.Context; private _records = new BPlusTree>(Private.recordCmp); } /** * The namespace for the `Table` class statics. */ export namespace Table { /** * A type alias for the table update type. */ export type Update = { readonly [recordId: string]: Record.Update; }; /** * A type alias for the table change type. */ export type Change = { readonly [recordId: string]: Record.Change; }; /** * A type alias for the table patch type. */ export type Patch = { readonly [recordId: string]: Record.Patch; }; /** * @internal * * A type alias for the table mutable change type. */ export type MutableChange = { [recordId: string]: Record.MutableChange; }; /** * @internal * * A type alias for the table mutable patch type. */ export type MutablePatch = { [recordId: string]: Record.MutablePatch; }; } /** * The namespace for the module implementation details. */ namespace Private { /** * A three-way record comparison function. */ export function recordCmp( a: Record, b: Record ): number { return StringExt.cmp(a.$id, b.$id); } /** * A three-way record id comparison function. */ export function recordIdCmp( record: Record, id: string ): number { return StringExt.cmp(record.$id, id); } /** * Create a new record object. * * @param schema - The schema for the record. * * @param id - The unique id for the record. * * @returns A new default initialized record. */ export function createRecord( schema: S, id: string ): Record { // Create the record and metadata objects. let record: any = {}; let metadata: any = {}; // Set the base record state. record.$id = id; record['@@metadata'] = metadata; // Populate the record and metadata. for (let name in schema.fields) { let field = schema.fields[name]; record[name] = field.createValue(); metadata[name] = field.createMetadata(); } // Return the new record. return record; } /** * Apply an update to a record. * * @param schema - The schema for the record. * * @param record - The record of interest. * * @param update - The update to apply to the record. * * @param context - The datastore context. * * @returns A new record with the update applied. */ export function applyUpdate( schema: S, record: Record, update: Record.Update, context: Datastore.Context ): Record { // Fetch the version and store id. let version = context.version; let storeId = context.storeId; // Fetch or create the table change and patch. let tc = context.change[schema.id] || (context.change[schema.id] = {}); let tp = context.patch[schema.id] || (context.patch[schema.id] = {}); // Fetch or create the record change and patch. let rc = tc[record.$id] || (tc[record.$id] = {}); let rp = tp[record.$id] || (tp[record.$id] = {}); // Cast the record to a value object. let previous = record as Record.Value; // Fetch the record metadata. let metadata = record['@@metadata']; // Create a clone of the record. let clone = { ...(record as any) }; // Iterate over the update. for (let name in update) { // Fetch the relevant field. let field = schema.fields[name]; // Apply the update for the field. let { value, change, patch } = field.applyUpdate({ previous: previous[name], update: update[name]!, metadata: metadata[name], version, storeId }); // Assign the new value to the clone. clone[name] = value; // Merge the change if needed. if (name in rc) { change = field.mergeChange(rc[name]!, change); } // Merge the patch if needed. if (name in rp) { patch = field.mergePatch(rp[name]!, patch); } // Update the record change and patch for the field. rc[name] = change; rp[name] = patch; } // Return the new record. return clone; } /** * A type alias for the result of a patch operation. */ export type PatchResult = { /** * The new record object. */ readonly record: Record; /** * The user-facing change object. */ readonly change: Record.Change; }; /** * Apply a patch to a record. * * @param schema - The schema for the record. * * @param record - The record of interest. * * @param patch - The patch to apply to the record. * * @return The result of applying the patch. */ export function applyPatch( schema: S, record: Record, patch: Record.Patch ): PatchResult { // Create the change object. let rc: Record.MutableChange = {}; // Cast the record to a value object. let previous = record as Record.Value; // Fetch the record metadata. let metadata = record['@@metadata']; // Create a clone of the record. let clone = { ...(record as any) }; // Iterate over the patch. for (let name in patch) { // Fetch the relevant field. let field = schema.fields[name]; // Apply the patch for the field. let { value, change } = field.applyPatch({ previous: previous[name], patch: patch[name]!, metadata: metadata[name] }); // Assign the new value to the clone. clone[name] = value; // Update the change object. rc[name] = change; } // Return the patch result. return { record: clone, change: rc }; } /** * Unapply a patch to a record. * * @param schema - The schema for the record. * * @param record - The record of interest. * * @param patch - The patch to unapply to the record. * * @return The result of unapplying the patch. */ export function unapplyPatch( schema: S, record: Record, patch: Record.Patch ): PatchResult { // Create the change object. let rc: Record.MutableChange = {}; // Cast the record to a value object. let previous = record as Record.Value; // Fetch the record metadata. let metadata = record['@@metadata']; // Create a clone of the record. let clone = { ...(record as any) }; // Iterate over the patch. for (let name in patch) { // Fetch the relevant field. let field = schema.fields[name]; // Apply the patch for the field. let { value, change } = field.unapplyPatch({ previous: previous[name], patch: patch[name]!, metadata: metadata[name] }); // Assign the new value to the clone. clone[name] = value; // Update the change object. rc[name] = change; } // Return the patch result. return { record: clone, change: rc }; } } lumino-2021.12.13/packages/datastore/src/textfield.ts000066400000000000000000000454071415564225700223260ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2018, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { ArrayExt, StringExt } from '@lumino/algorithm'; import { Field } from './field'; import { createTriplexIds } from './utilities'; /** * A field which represents collaborative text. */ export class TextField extends Field< TextField.Value, TextField.Update, TextField.Metadata, TextField.Change, TextField.Patch > { /** * Construct a new text field. * * @param options - The options for initializing the field. */ constructor(options: TextField.IOptions = {}) { super(options); } /** * The discriminated type of the field. */ get type(): 'text' { return 'text'; } /** * Create the initial value for the field. * * @returns The initial value for the field. */ createValue(): TextField.Value { return ''; } /** * Create the metadata for the field. * * @returns The metadata for the field. */ createMetadata(): TextField.Metadata { return { ids: [], cemetery: {} }; } /** * Apply a user update to the field. * * @param args - The arguments for the update. * * @returns The result of applying the update. */ applyUpdate( args: Field.UpdateArgs< TextField.Value, TextField.Update, TextField.Metadata > ): Field.UpdateResult { // Unpack the arguments. let { previous, update, metadata, version, storeId } = args; // Set up a variable to hold the current value. let value = previous; // Set up the change and patch arrays. let change: TextField.ChangePart[] = []; let patch: TextField.PatchPart[] = []; // Coerce the update into an array of splices. if (Private.isSplice(update)) { update = [update]; } // Iterate over the update. for (let splice of update) { // Apply the splice to the value. let obj = Private.applySplice(value, splice, metadata, version, storeId); // Update the change array. change.push(obj.change); // Update the patch array. patch.push(obj.patch); // Update the current value. value = obj.value; } // Return the update result. return { value, change, patch }; } /** * Apply a system patch to the field. * * @param args - The arguments for the patch. * * @returns The result of applying the patch. */ applyPatch( args: Field.PatchArgs ): Field.PatchResult { // Unpack the arguments. let { previous, patch, metadata } = args; // Set up a variable to hold the current value. let value = previous; // Set up the change array. let change: TextField.ChangePart[] = []; // Iterate over the patch. for (let part of patch) { // Apply the patch part to the value. let obj = Private.applyPatch(value, part, metadata); // Update the change array. change.push(...obj.change); // Update the current value. value = obj.value; } // Return the patch result. return { value, change }; } /** * Unapply a system patch to the field. * * @param args - The arguments for the patch. * * @returns The result of unapplying the patch. */ unapplyPatch( args: Field.PatchArgs ): Field.PatchResult { // Unpack the arguments. let { previous, patch, metadata } = args; // Set up a variable to hold the current value. let value = previous; // Set up the change array. let change: TextField.ChangePart[] = []; // Iterate over the patch. for (let part of patch) { let reversed = { removedIds: part.insertedIds, insertedIds: part.removedIds, removedText: part.insertedText, insertedText: part.removedText }; // Apply the patch part to the value. let obj = Private.applyPatch(value, reversed, metadata); // Update the change array. change.push(...obj.change); // Update the current value. value = obj.value; } // Return the patch result. return { value, change }; } /** * Merge two change objects into a single change object. * * @param first - The first change object of interest. * * @param second - The second change object of interest. * * @returns A new change object which represents both changes. */ mergeChange( first: TextField.Change, second: TextField.Change ): TextField.Change { return [...first, ...second]; } /** * Merge two patch objects into a single patch object. * * @param first - The first patch object of interest. * * @param second - The second patch object of interest. * * @returns A new patch object which represents both patches. */ mergePatch(first: TextField.Patch, second: TextField.Patch): TextField.Patch { return [...first, ...second]; } } /** * The namespace for the `TextField` class statics. */ export namespace TextField { /** * An options object for initializing a text field. */ export interface IOptions extends Field.IOptions {} /** * A type alias for the value type of a text field. */ export type Value = string; /** * A type alias for a text field splice. */ export type Splice = { /** * The index of the splice. */ readonly index: number; /** * The number of characters to remove. */ readonly remove: number; /** * The text to insert. */ readonly text: string; }; /** * A type alias for the text field update type. */ export type Update = Splice | ReadonlyArray; /** * A type alias for the text field metadata type. */ export type Metadata = { /** * An array of ids corresponding to the text characters. */ readonly ids: Array; /** * The cemetery for concurrently deleted characters. */ readonly cemetery: { [id: string]: number }; }; /** * A type alias for a text field change part. */ export type ChangePart = { /** * The index of the modification. */ readonly index: number; /** * The text that was removed. */ readonly removed: string; /** * The text that was inserted. */ readonly inserted: string; }; /** * A type alias for the text field change type. */ export type Change = ReadonlyArray; /** * A type alias for the text field patch part. */ export type PatchPart = { /** * The ids that were removed. */ readonly removedIds: ReadonlyArray; /** * The text that was removed. */ readonly removedText: string; /** * The ids that were inserted. */ readonly insertedIds: ReadonlyArray; /** * The text that was inserted. */ readonly insertedText: string; }; /** * A type alias for the text field patch type. */ export type Patch = ReadonlyArray; } /** * The namespace for the module implementation details. */ namespace Private { /** * A type-guard function for a text field update type. */ export function isSplice(value: TextField.Update): value is TextField.Splice { return !Array.isArray(value); } /** * A type alias for the result of a splice operation. */ export type SpliceResult = { /** * The user-facing change part for the splice. */ readonly change: TextField.ChangePart; /** * The system-facing patch part for the splice. */ readonly patch: TextField.PatchPart; /** * The new value of the text. */ readonly value: string; }; /** * Apply a splice to a text field. * * @param value - The current value of the field. * * @param splice - The splice to apply to the field. * * @param metadata - The metadata for the field. * * @param version - The current datastore version. * * @param storeId - The unique id of the datastore. * * @returns The result of the splice operation. */ export function applySplice( value: string, splice: TextField.Splice, metadata: TextField.Metadata, version: number, storeId: number ): SpliceResult { // Unpack the splice. let { index, remove, text } = splice; // Clamp the index to the string bounds. if (index < 0) { index = Math.max(0, index + value.length); } else { index = Math.min(index, value.length); } // Clamp the remove count to the string bounds. let count = Math.min(remove, value.length - index); // Fetch the lower and upper identifiers. let lower = index === 0 ? '' : metadata.ids[index - 1]; let upper = index === value.length ? '' : metadata.ids[index]; // Create the ids for the splice. let ids = createTriplexIds(text.length, version, storeId, lower, upper); // Apply the splice to the ids. let removedIds = spliceArray(metadata.ids, index, count, ids); // Compute the removed text. let removedText = value.slice(index, index + count); // Create the change object. let change = { index, removed: removedText, inserted: text }; // Create the patch object. let patch = { removedIds, removedText, insertedIds: ids, insertedText: text }; // Compute the new value. value = value.slice(0, index) + text + value.slice(index + count); // Return the splice result. return { change, patch, value }; } /** * A type alias for the result of a patch operation. */ export type PatchResult = { /** * The user-facing change for the patch. */ readonly change: TextField.Change; /** * The new value of the text. */ readonly value: string; }; /** * Apply a patch to a text field. * * @param value - The current value of the field. * * @param patch - The patch part to apply to the field. * * @param metadata - The metadata for the field. * * @returns The user-facing change array for the patch. */ export function applyPatch( value: string, patch: TextField.PatchPart, metadata: TextField.Metadata ): PatchResult { // Unpack the patch. let { removedIds, insertedIds, insertedText } = patch; // Set up the change array. let change: TextField.ChangePart[] = []; // Process the removed identifiers, if necessary. if (removedIds.length > 0) { // Chunkify the removed identifiers, // or increment the removed ids in the cemetery. let chunks = findRemovedChunks(removedIds, metadata); // Process the chunks. while (chunks.length > 0) { // Pop the last-most chunk. let { index, count } = chunks.pop()!; // Remove the identifiers from the metadata. metadata.ids.splice(index, count); // Compute the removed text let removed = value.slice(index, index + count); // Compute the new value. value = value.slice(0, index) + value.slice(index + count); // Add the change part to the change array. change.push({ index, removed, inserted: '' }); } } // Process the inserted identifiers, if necessary. if (insertedIds.length > 0) { // Chunkify the inserted identifiers, or decrement the removed // ids in the cemetery. let chunks = findInsertedChunks(insertedIds, insertedText, metadata); // Process the chunks. while (chunks.length > 0) { // Pop the last-most chunk. let { index, ids, text } = chunks.pop()!; // Insert the identifiers into the metadata. spliceArray(metadata.ids, index, 0, ids); // Insert the text into the value. value = value.slice(0, index) + text + value.slice(index); // Add the change part to the change array. change.push({ index, removed: '', inserted: text }); } } // Return the change array. return { change, value }; } /** * A type alias for a remove chunk. */ type RemoveChunk = { // The index of the removal. index: number; // The number of elements to remove. count: number; }; /** * Convert an array of identifiers into removal chunks. * * @param ids - The ids to remove from the metadta. * * @param metadata - The metadata for the text field. * * @returns The ordered chunks to remove. * * #### Notes * The metadata may be mutated if concurrently removed chunks are encountered. */ function findRemovedChunks( ids: ReadonlyArray, metadata: TextField.Metadata ): RemoveChunk[] { // Set up the chunks array. let chunks: RemoveChunk[] = []; // Set up the iteration index. let i = 0; // Fetch the identifier array length. let n = ids.length; // Iterate over the identifiers to remove. while (i < n) { // Find the boundary identifier for the current id. let j = ArrayExt.lowerBound(metadata.ids, ids[i], StringExt.cmp); // If the boundary is at the end of the array, or if the boundary id // does not match the id we are looking for, then we are dealing with // a concurrently deleted value. In that case, increment its reference // in the cemetery and continue processing ids. if (j === metadata.ids.length || metadata.ids[j] !== ids[i]) { let count = metadata.cemetery[ids[i]] || 0; metadata.cemetery[ids[i]] = count + 1; i++; continue; } // Set up the chunk index. let index = j; // Set up the chunk count. let count = 0; // Find the extent of the chunk. while (i < n && StringExt.cmp(ids[i], metadata.ids[j]) === 0) { count++; i++; j++; } // Add the chunk to the chunks array, or bump the id index. if (count > 0) { chunks.push({ index, count }); } else { i++; } } // Return the computed chunks. return chunks; } /** * A type alias for an insert chunk. */ type InsertChunk = { // The index of the insert. index: number; // The identifiers to insert. ids: string[]; // The text to insert. text: string; }; /** * Convert arrays of identifiers and values into insert chunks. * * @param ids - The ids to be inserted. * * @param text - The text to be inserted. * * @param metadata - The metadata for the text field. * * @returns The ordered chunks to insert. * * #### Notes * The metadata may be mutated if concurrently removed chunks are encountered. */ function findInsertedChunks( ids: ReadonlyArray, text: string, metadata: TextField.Metadata ): InsertChunk[] { let indices: number[] = []; let insertIds: string[] = []; let insertText = ''; for (let i = 0; i < ids.length; i++) { // Check if the id has been concurrently deleted. If so, update // the cemetery, and continue processing without inserting the id. if (checkCemeteryForInsert(ids[i], metadata.cemetery)) { continue; } // Add the id to the ids which will be actually inserted. insertIds.push(ids[i]); indices.push(ArrayExt.lowerBound(metadata.ids, ids[i], StringExt.cmp)); insertText += text[i]; } return chunkifyInsertions(insertIds, insertText, indices); } /** * Consolidate inserted IDs into a set of chunks so that we can splice them * into the existing value with a minimal number of splices. * * @param ids - The ids to be inserted. * * @param text - The text to be inserted. Should be the same length as ids. * * @param indices - The indices at which to insert the text. Should be the same length as ids. * * @returns The ordered chunks to insert. */ function chunkifyInsertions( ids: ReadonlyArray, text: string, indices: ReadonlyArray ): InsertChunk[] { // Set up the chunks array. let chunks: InsertChunk[] = []; // Set up the loop over the ids to insert. let insertIndex: number; let i = 0; while (i < ids.length) { // Reset the insert chunk data let chunkIds: string[] = []; let chunkText = ''; insertIndex = indices[i]; // Find the extent of the chunk while (indices[i] === insertIndex && i < ids.length) { chunkIds.push(ids[i]); chunkText += text[i]; i++; } if (chunkText) { chunks.push({ index: insertIndex, ids: chunkIds, text: chunkText }); } } return chunks; } /** * Check if an id should be inserted, or if it has been concurrently deleted. * * @param id - the id to check. * * @param cemetery - the cemetery which determines whether the id should be inserted. * * @returns whether the id was found, indicating that it shouldn't be inserted. * * #### Notes * If the ID *is* found in the cemetery, its value in the cemetery is decremented, * reflecting that it is closer to being shown. */ function checkCemeteryForInsert( id: string, cemetery: { [x: string]: number } ): boolean { let count = cemetery[id] || 0; if (count === 1) { delete cemetery[id]; return true; } if (count > 1) { cemetery[id] = count - 1; return true; } return false; } /** * Splice data into an array. * * #### Notes * This is intentionally similar to Array.splice, but chunks the splices into * multiple splices so that it does not crash if the number of spliced IDs * is greater than the maximum number of arguments for a function. * * @param arr - the array on which to perform the splice. * * @param start - the start index for the splice. * * @param deleteCount - how many indices to remove. * * @param items - the items to splice into the array. * * @returns an array of the deleted elements. */ function spliceArray( arr: T[], start: number, deleteCount?: number, items?: ReadonlyArray ): ReadonlyArray { if (!items) { return arr.splice(start, deleteCount); } let size = 100000; if (items.length < size) { return arr.splice(start, deleteCount || 0, ...items); } let deleted = arr.splice(start, deleteCount); let n = Math.floor(items.length / size); let idx = 0; for (let i = 0; i < n; i++, idx += size) { arr.splice(idx, 0, ...items.slice(idx, size)); } arr.splice(idx, 0, ...items.slice(idx)); return deleted; } } lumino-2021.12.13/packages/datastore/src/utilities.ts000066400000000000000000000201371415564225700223420ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2018, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ /** * Create a duplex string identifier. * * @param version - The datastore version for the duplex id. * * @param store - The datastore id for the duplex id. * * @returns A string duplex id for the given arguments. * * #### Notes * ID format: <6-byte version><4-byte storeId> */ export function createDuplexId(version: number, store: number): string { // Split the version into 16-bit values. let vc = version & 0xffff; let vb = (((version - vc) / 0x10000) | 0) & 0xffff; let va = (((version - vb - vc) / 0x100000000) | 0) & 0xffff; // Split the store id into 16-bit values. let sb = store & 0xffff; let sa = (((store - sb) / 0x10000) | 0) & 0xffff; // Convert the parts into a string identifier duplex. return String.fromCharCode(va, vb, vc, sa, sb); } /** * Create a triplex string identifier between two boundaries. * * @param version - The datastore version for the triplex id. * * @param store - The datastore id for the triplex id. * * @param lower - The lower triplex boundary identifier or `''` * to represent the lowest-most boundary. * * @param upper - The upper triplex boundary identifier or `''` * to represent the upper-most boundary. * * @returns A new triplex identifier between the two boundaries. * * #### Notes * ID format: <6-byte path><6-byte version><4-byte storeId> * (N >= 1) */ export function createTriplexId( version: number, store: number, lower: string, upper: string ): string { // The maximum path in a triplex id. const MAX_PATH = 0xffffffffffff; // Set up the variable to hold the id. let id = ''; // Fetch the triplet counts of the ids. let lowerCount = lower ? Private.idTripletCount(lower) : 0; let upperCount = upper ? Private.idTripletCount(upper) : 0; // Iterate over the id triplets. for (let i = 0, n = Math.max(lowerCount, upperCount); i < n; ++i) { // Fetch the lower identifier triplet, padding as needed. let lp: number; let lc: number; let ls: number; if (i >= lowerCount) { lp = 0; lc = 0; ls = 0; } else { lp = Private.idPathAt(lower, i); lc = Private.idVersionAt(lower, i); ls = Private.idStoreAt(lower, i); } // Fetch the upper identifier triplet, padding as needed. let up: number; let uc: number; let us: number; if (i >= upperCount) { up = upperCount === 0 ? MAX_PATH + 1 : 0; uc = 0; us = 0; } else { up = Private.idPathAt(upper, i); uc = Private.idVersionAt(upper, i); us = Private.idStoreAt(upper, i); } // If the triplets are the same, copy the triplet and continue. if (lp === up && lc === uc && ls === us) { id += Private.createTriplet(lp, lc, ls); continue; } // If the triplets are different, the well-ordered identifiers // assumption means that the lower triplet compares less than // the upper triplet. The task now is to find the nearest free // path slot among the remaining triplets. // If there is free space between the path portions of the // triplets, select a new path which falls between them. if (up - lp > 1) { let np = Private.randomPath(lp + 1, up - 1); id += Private.createTriplet(np, version, store); return id.slice(); } // Otherwise, copy the left triplet and reset the upper count // to zero so that the loop chooses the nearest available path // slot after the current lower triplet. id += Private.createTriplet(lp, lc, ls); upperCount = 0; } // If this point is reached, the lower and upper identifiers share // the same path but diverge based on the version or store id. It is // safe to insert anywhere in an extra triplet. let np = Private.randomPath(1, MAX_PATH); id += Private.createTriplet(np, version, store); return id.slice(); } /** * Create the multiple triplex identifiers. * * @param n - The number of identifiers to create. * * @param version - The datastore version. * * @param store - The datastore id. * * @param lower - The lower boundary identifier, exclusive. * * @param uppper - The upper boundary identifier, exclusive. * * @returns The requested identifiers. */ export function createTriplexIds( n: number, version: number, store: number, lower: string, upper: string ): string[] { // Initialize the identifiers array. let ids: string[] = []; // Loop the required number of times. while (ids.length < n) { // Create an identifier between the boundaries. let id = createTriplexId(version, store, lower, upper); // Add the identifier to the array. ids.push(id); // Update the lower boundary identifier. lower = id; } // Return the generated identifiers. return ids; } /** * The namespace for the module implementation details. */ namespace Private { /** * Create a string identifier triplet. * * @param path - The path value for the triplet. * * @param version - The version for the triplet. * * @param store - The store id for the triplet. * * @returns The string identifier triplet. */ export function createTriplet( path: number, version: number, store: number ): string { // Split the path into 16-bit values. let pc = path & 0xffff; let pb = (((path - pc) / 0x10000) | 0) & 0xffff; let pa = (((path - pb - pc) / 0x100000000) | 0) & 0xffff; // Split the version into 16-bit values. let vc = version & 0xffff; let vb = (((version - vc) / 0x10000) | 0) & 0xffff; let va = (((version - vb - vc) / 0x100000000) | 0) & 0xffff; // Split the store id into 16-bit values. let sb = store & 0xffff; let sa = (((store - sb) / 0x10000) | 0) & 0xffff; // Convert the parts into a string identifier triplet. return String.fromCharCode(pa, pb, pc, va, vb, vc, sa, sb); } /** * Get the total number of path triplets in an identifier. * * @param id - The identifier of interest. * * @returns The total number of triplets in the id. */ export function idTripletCount(id: string): number { return id.length >> 3; } /** * Get the path value for a particular triplet. * * @param id - The string id of interest. * * @param i - The index of the triplet. * * @returns The path value for the specified triplet. */ export function idPathAt(id: string, i: number): number { let j = i << 3; let a = id.charCodeAt(j + 0); let b = id.charCodeAt(j + 1); let c = id.charCodeAt(j + 2); return a * 0x100000000 + b * 0x10000 + c; } /** * Get the version for a particular triplet. * * @param id - The identifier of interest. * * @param i - The index of the triplet. * * @returns The version for the specified triplet. */ export function idVersionAt(id: string, i: number): number { let j = i << 3; let a = id.charCodeAt(j + 3); let b = id.charCodeAt(j + 4); let c = id.charCodeAt(j + 5); return a * 0x100000000 + b * 0x10000 + c; } /** * Get the store id for a particular triplet. * * @param id - The identifier of interest. * * @param i - The index of the triplet. * * @returns The store id for the specified triplet. */ export function idStoreAt(id: string, i: number): number { let j = i << 3; let a = id.charCodeAt(j + 6); let b = id.charCodeAt(j + 7); return a * 0x10000 + b; } /** * Pick a path in the leading bucket of an inclusive range. * * @param min - The minimum allowed path, inclusive. * * @param max - The maximum allowed path, inclusive. * * @returns A random path in the leading bucket of the range. */ export function randomPath(min: number, max: number): number { return min + Math.round(Math.random() * Math.sqrt(max - min)); } } lumino-2021.12.13/packages/datastore/tdoptions.json000066400000000000000000000005201415564225700221000ustar00rootroot00000000000000{ "excludeNotExported": true, "mode": "file", "target": "es5", "module": "es5", "lib": [ "lib.es2015.d.ts", "lib.es2015.collection.d.ts", "lib.es2015.promise.d.ts", "lib.dom.d.ts" ], "out": "../../docs/source/api/datastore", "baseUrl": ".", "paths": { "@lumino/*": ["node_modules/@lumino/*"] } } lumino-2021.12.13/packages/datastore/tests/000077500000000000000000000000001415564225700203275ustar00rootroot00000000000000lumino-2021.12.13/packages/datastore/tests/karma.conf.js000066400000000000000000000005051415564225700227040ustar00rootroot00000000000000module.exports = function (config) { config.set({ basePath: '.', frameworks: ['mocha'], reporters: ['mocha'], files: ['build/bundle.test.js'], port: 9876, colors: true, singleRun: true, browserNoActivityTimeout: 30000, failOnEmptyTestSuite: false, logLevel: config.LOG_INFO }); }; lumino-2021.12.13/packages/datastore/tests/package.json000066400000000000000000000021751415564225700226220ustar00rootroot00000000000000{ "private": true, "name": "@lumino/test-datastore", "version": "0.1.0", "dependencies": { "@lumino/algorithm": "^1.1.2", "@lumino/coreutils": "^1.3.1", "@lumino/datastore": "^0.7.2", "@lumino/messaging": "^1.2.2", "@lumino/signaling": "^1.2.3", "chai": "^3.5.0", "mocha": "^3.2.0" }, "devDependencies": { "@microsoft/api-extractor": "^7.6.0", "@types/chai": "^3.4.35", "@types/mocha": "^2.2.39", "karma": "^1.5.0", "karma-chrome-launcher": "^2.0.0", "karma-firefox-launcher": "^1.0.0", "karma-ie-launcher": "^1.0.0", "karma-mocha": "^1.3.0", "karma-mocha-reporter": "^2.2.2", "rimraf": "^2.5.2", "typescript": "~3.6.4", "webpack": "^2.2.1" }, "scripts": { "build": "tsc && webpack", "clean": "rimraf build", "test": "npm run test:firefox-headless", "test:chrome": "karma start --browsers=Chrome", "test:chrome-headless": "karma start --browsers=ChromeHeadless", "test:firefox": "karma start --browsers=Firefox", "test:firefox-headless": "karma start --browsers=FirefoxHeadless", "test:ie": "karma start --browsers=IE" } } lumino-2021.12.13/packages/datastore/tests/src/000077500000000000000000000000001415564225700211165ustar00rootroot00000000000000lumino-2021.12.13/packages/datastore/tests/src/datastore.spec.ts000066400000000000000000000611401415564225700244070ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2019, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { expect } from 'chai'; import { Datastore, Fields, IServerAdapter, ListField, MapField, RegisterField, TextField } from '@lumino/datastore'; import { MessageLoop } from '@lumino/messaging'; import { Signal } from '@lumino/signaling'; type CustomMetadata = { id: string }; type TestSchema = { id: string; fields: { content: TextField; count: RegisterField; enabled: RegisterField; tags: MapField; links: ListField; metadata: RegisterField; }; }; let schema1: TestSchema = { id: 'test-schema-1', fields: { content: Fields.Text(), count: Fields.Number(), enabled: Fields.Boolean(), tags: Fields.Map(), links: Fields.List(), metadata: Fields.Register({ value: { id: 'identifier' } }) } }; let schema2: TestSchema = { id: 'test-schema-2', fields: { content: Fields.Text(), count: Fields.Number(), enabled: Fields.Boolean(), tags: Fields.Map(), links: Fields.List(), metadata: Fields.Register({ value: { id: 'identifier' } }) } }; let state = { [schema1.id]: [ { content: 'Lorem Ipsum', count: 42, enabled: true, links: ['www.example.com'], metadata: { id: 'myidentifier' } } ], [schema2.id]: [ { content: 'Ipsum Lorem', count: 33, enabled: false, links: ['www.example.com', 'https://github.com/lumino/luminojs'], metadata: null } ] }; /** * Return a shuffled copy of an array */ function shuffle(array: ReadonlyArray): T[] { let ret = array.slice(); for (let i = ret.length - 1; i > 0; i--) { let j = Math.floor(Math.random() * (i + 1)); // random index from 0 to i [ret[i], ret[j]] = [ret[j], ret[i]]; // swap elements } return ret; } /** * An in-memory implementation of a patch store. */ class InMemoryServerAdapter implements IServerAdapter { constructor() { this.onRemoteTransaction = null; this.onUndo = null; this.onRedo = null; } broadcast(transaction: Datastore.Transaction): void { this._transactions[transaction.id] = transaction; } undo(id: string): Promise { if (this.onUndo) { this.onUndo(this._transactions[id]); } return Promise.resolve(undefined); } redo(id: string): Promise { if (this.onRedo) { this.onRedo(this._transactions[id]); } return Promise.resolve(undefined); } onRemoteTransaction: ((transaction: Datastore.Transaction) => void) | null; onUndo: ((transaction: Datastore.Transaction) => void) | null; onRedo: ((transaction: Datastore.Transaction) => void) | null; get transactions(): { [id: string]: Datastore.Transaction } { return this._transactions; } get isDisposed(): boolean { return this._isDisposed; } dispose(): void { if (this._isDisposed) { return; } this._isDisposed = true; Signal.clearData(this); } private _isDisposed = false; private _transactions: { [id: string]: Datastore.Transaction } = {}; } describe('@lumino/datastore', () => { describe('Datastore', () => { let datastore: Datastore; let adapter: InMemoryServerAdapter; const DATASTORE_ID = 1234; beforeEach(() => { adapter = new InMemoryServerAdapter(); datastore = Datastore.create({ id: DATASTORE_ID, schemas: [schema1, schema2], adapter }); }); afterEach(() => { datastore.dispose(); adapter.dispose(); }); describe('create()', () => { it('should create a new datastore', () => { let datastore = Datastore.create({ id: 1, schemas: [schema1] }); expect(datastore).to.be.instanceof(Datastore); }); it('should throw an error for an invalid schema', () => { let invalid1 = { id: 'invalid-schema', fields: { '@content': Fields.Text() } }; expect(() => { Datastore.create({ id: 1, schemas: [invalid1] }); }).to.throw(/validation failed/); let invalid2 = { id: 'invalid-schema', fields: { $content: Fields.Text() } }; expect(() => { Datastore.create({ id: 1, schemas: [invalid2] }); }).to.throw(/validation failed/); }); it('should restore valid state', () => { let datastore = Datastore.create({ id: 1, schemas: [schema1, schema2], restoreState: JSON.stringify(state) }); let reexport = datastore.toString(); expect(JSON.parse(reexport)).to.eql(state); }); it('should restore partial state', () => { let partialState = { [schema1.id]: state[schema1.id] }; let datastore = Datastore.create({ id: 1, schemas: [schema1, schema2], restoreState: JSON.stringify(partialState) }); let reexport = datastore.toString(); expect(JSON.parse(reexport)).to.eql({ ...partialState, [schema2.id]: [] }); }); }); describe('dispose()', () => { it('should dispose of the resources held by the datastore', () => { expect(datastore.adapter).to.not.be.null; datastore.dispose(); expect(datastore.adapter).to.be.null; }); it('should be safe to call more than once', () => { datastore.dispose(); datastore.dispose(); }); }); describe('isDisposed()', () => { it('should indicate whether the datastore is disposed', () => { expect(datastore.isDisposed).to.be.false; datastore.dispose(); expect(datastore.isDisposed).to.be.true; }); }); describe('changed', () => { it('should should emit upon changes to the datastore', () => { let called = false; let id = ''; datastore.changed.connect((_, change) => { called = true; expect(change.type).to.equal('transaction'); expect(change.transactionId).to.equal(id); expect(change.storeId).to.equal(DATASTORE_ID); expect(change.change['test-schema-1']).to.not.be.undefined; expect(change.change['test-schema-2']).to.be.undefined; }); let t1 = datastore.get(schema1); id = datastore.beginTransaction(); t1.update({ 'my-record': { enabled: true } }); datastore.endTransaction(); expect(called).to.be.true; }); }); describe('id', () => { it('should return the unique store id', () => { expect(datastore.id).to.equal(DATASTORE_ID); }); }); describe('inTransaction', () => { it('should indicate whether the datastore is in a transaction', () => { expect(datastore.inTransaction).to.be.false; datastore.beginTransaction(); expect(datastore.inTransaction).to.be.true; datastore.endTransaction(); expect(datastore.inTransaction).to.be.false; }); }); describe('adapter', () => { it('should be the adapter for the datastore', () => { expect(datastore.adapter).to.equal(adapter); }); it('should recieve transactions from the datastore', () => { let t2 = datastore.get(schema2); datastore.beginTransaction(); t2.update({ 'my-record': { enabled: true } }); expect(Object.keys(adapter.transactions).length).to.equal(0); datastore.endTransaction(); expect(Object.keys(adapter.transactions).length).to.equal(1); }); }); describe('version', () => { it('should increase with each transaction', () => { let version = datastore.version; let t1 = datastore.get(schema1); let t2 = datastore.get(schema2); datastore.beginTransaction(); t2.update({ 'my-record': { enabled: true } }); datastore.endTransaction(); expect(datastore.version).to.be.above(version); version = datastore.version; datastore.beginTransaction(); t1.update({ 'my-record': { enabled: true } }); datastore.endTransaction(); expect(datastore.version).to.be.above(version); }); }); describe('iter()', () => { it('should return an iterator over the tables of the datastore', () => { let iterator = datastore.iter(); let t1 = iterator.next(); let t2 = iterator.next(); expect(t1!.schema).to.equal(schema1); expect(t2!.schema).to.equal(schema2); expect(iterator.next()).to.be.undefined; }); }); describe('get()', () => { it('should return a table for a schema', () => { let t1 = datastore.get(schema1); let t2 = datastore.get(schema2); expect(t1.schema).to.equal(schema1); expect(t2.schema).to.equal(schema2); }); it('should throw an error for a nonexistent schema', () => { let schema3 = { ...schema2, id: 'new-schema' }; expect(() => { datastore.get(schema3); }).to.throw(/No table found/); }); }); describe('beginTransaction()', () => { it('should allow for mutations on the datastore', () => { let t1 = datastore.get(schema1); expect(datastore.inTransaction).to.be.false; expect(() => { t1.update({ 'my-record': { enabled: true } }); }).to.throw(/A table can only be updated/); datastore.beginTransaction(); t1.update({ 'my-record': { enabled: true } }); expect(datastore.inTransaction).to.be.true; datastore.endTransaction(); expect(datastore.inTransaction).to.be.false; }); it('should return a transaction id', () => { expect(datastore.beginTransaction()).to.not.equal(''); datastore.endTransaction(); }); it('should throw if called multiple times', () => { datastore.beginTransaction(); expect(() => datastore.beginTransaction()).to.throw(/Already/); datastore.endTransaction(); }); it('should queue additional transactions for processing later', async () => { // First, generate a transaction to apply later. let id = datastore.beginTransaction(); let t2 = datastore.get(schema2); t2.update({ 'my-record': { content: { index: 0, remove: 0, text: 'hello, world' } } }); datastore.endTransaction(); // Create a new datastore let adapter2 = new InMemoryServerAdapter(); let datastore2 = Datastore.create({ id: 5678, schemas: [schema1, schema2], adapter: adapter2 }); // Begin a transaction in the new datastore datastore2.beginTransaction(); t2 = datastore2.get(schema2); t2.update({ 'my-record': { enabled: true } }); // Trigger a remote transaction. It should be queued. adapter2.onRemoteTransaction!(adapter.transactions[id]); datastore2.endTransaction(); expect(t2.get('my-record')!.enabled).to.be.true; expect(t2.get('my-record')!.content).to.equal(''); // Flush the message loop to trigger processing of the queue. MessageLoop.flush(); expect(t2.get('my-record')!.enabled).to.be.true; expect(t2.get('my-record')!.content).to.equal('hello, world'); datastore2.dispose(); adapter2.dispose(); }); it('should automatically close a transaction later', async () => { datastore.beginTransaction(); expect(datastore.inTransaction).to.be.true; // Flush the message loop to trigger processing of the queue. MessageLoop.flush(); expect(datastore.inTransaction).to.be.false; }); }); describe('endTransaction()', () => { it('should emit a changed signal with the user-facing changes', () => { let called = false; let id = ''; datastore.changed.connect((_, change) => { called = true; expect(change.type).to.equal('transaction'); expect(change.transactionId).to.equal(id); expect(change.storeId).to.equal(DATASTORE_ID); expect(change.change['test-schema-2']).to.not.be.undefined; expect(change.change['test-schema-1']).to.be.undefined; }); let t2 = datastore.get(schema2); id = datastore.beginTransaction(); t2.update({ 'my-record': { enabled: true } }); datastore.endTransaction(); expect(called).to.be.true; }); it('should broadcast the transaction to the server adapter', () => { let t2 = datastore.get(schema2); datastore.beginTransaction(); t2.update({ 'my-record': { enabled: true } }); expect(Object.keys(adapter.transactions).length).to.equal(0); datastore.endTransaction(); expect(Object.keys(adapter.transactions).length).to.equal(1); }); it('should throw if there is not a transaction begun', () => { expect(() => datastore.endTransaction()).to.throw(/No transaction/); }); }); describe('undo()', () => { it('should be throw without a patch server', async () => { let datastore = Datastore.create({ id: DATASTORE_ID, schemas: [schema1, schema2] }); let thrown = false; try { await datastore.undo(''); } catch { thrown = true; } finally { expect(thrown).to.be.true; datastore.dispose(); } }); it('should throw if invoked during a transaction', async () => { let thrown = false; try { datastore.beginTransaction(); await datastore.undo(''); } catch { thrown = true; } finally { datastore.endTransaction(); expect(thrown).to.be.true; } }); it('should unapply a transaction by id', async () => { let t2 = datastore.get(schema2); let id = datastore.beginTransaction(); t2.update({ 'my-record': { enabled: true } }); datastore.endTransaction(); let record = t2.get('my-record')!; expect(record.enabled).to.be.true; await datastore.undo(id); record = t2.get('my-record')!; expect(record.enabled).to.be.false; }); it('should allow for multiple undos', async () => { let t2 = datastore.get(schema2); let id = datastore.beginTransaction(); t2.update({ 'my-record': { enabled: true } }); datastore.endTransaction(); await datastore.undo(id); await datastore.undo(id); await datastore.undo(id); let record = t2.get('my-record')!; expect(record.enabled).to.be.false; }); it('should allow for concurrent undos', async () => { // Create a transaction. let t2 = datastore.get(schema2); let id = datastore.beginTransaction(); t2.update({ 'my-record': { enabled: true, content: { index: 0, remove: 0, text: 'hello, world' } } }); datastore.endTransaction(); let transaction = adapter.transactions[id]; // Create a new datastore and undo the transaction before it's applied. let datastore2 = Datastore.create({ id: 5678, schemas: [schema1, schema2], adapter }); t2 = datastore2.get(schema2); // No change for concurrently undone transaction. adapter.onUndo!(transaction); let record = t2.get('my-record'); expect(record).to.be.undefined; // Now apply the transaction, it should be undone still adapter.onRemoteTransaction!(transaction); record = t2.get('my-record')!; expect(record).to.be.undefined; // Now redo the transaction, it should appear. adapter.onRedo!(transaction); record = t2.get('my-record')!; expect(record.enabled).to.be.true; expect(record.content).to.equal('hello, world'); }); it('should emit a changed signal when undoing a change', async () => { let t2 = datastore.get(schema2); let id = datastore.beginTransaction(); t2.update({ 'my-record': { enabled: true } }); datastore.endTransaction(); let record = t2.get('my-record')!; expect(record.enabled).to.be.true; let called = false; datastore.changed.connect((sender, args) => { expect(args.type).to.equal('undo'); expect(args.transactionId).to.equal(id); called = true; }); await datastore.undo(id); expect(called).to.be.true; }); it('should not emit a changed signal when there is nothing to undo', async () => { let t2 = datastore.get(schema2); let id = datastore.beginTransaction(); t2.update({ 'my-record': { enabled: true } }); datastore.endTransaction(); let record = t2.get('my-record')!; expect(record.enabled).to.be.true; await datastore.undo(id); let called = false; datastore.changed.connect((sender, args) => { called = true; }); // Already undone, so no signal should be emitted. await datastore.undo(id); expect(called).to.be.false; }); }); describe('redo()', () => { it('should be throw without a patch server', async () => { let datastore = Datastore.create({ id: DATASTORE_ID, schemas: [schema1, schema2] }); let thrown = false; try { await datastore.redo(''); } catch { thrown = true; } finally { expect(thrown).to.be.true; datastore.dispose(); } }); it('should throw if invoked during a transaction', async () => { let thrown = false; try { datastore.beginTransaction(); await datastore.redo(''); } catch { thrown = true; } finally { datastore.endTransaction(); expect(thrown).to.be.true; } }); it('should reapply a transaction by id', async () => { let t2 = datastore.get(schema2); let id = datastore.beginTransaction(); t2.update({ 'my-record': { enabled: true, content: { index: 0, remove: 0, text: 'hello, world' } } }); datastore.endTransaction(); let record = t2.get('my-record')!; expect(record.enabled).to.be.true; expect(record.content).to.equal('hello, world'); await datastore.undo(id); record = t2.get('my-record')!; expect(record.enabled).to.be.false; expect(record.content).to.equal(''); await datastore.redo(id); record = t2.get('my-record')!; expect(record.enabled).to.be.true; expect(record.content).to.equal('hello, world'); }); it('should have redos winning in a tie', async () => { let t2 = datastore.get(schema2); let id = datastore.beginTransaction(); t2.update({ 'my-record': { enabled: true, content: { index: 0, remove: 0, text: 'hello, world' } } }); datastore.endTransaction(); let record = t2.get('my-record')!; expect(record.enabled).to.be.true; expect(record.content).to.equal('hello, world'); await datastore.undo(id); await datastore.undo(id); await datastore.undo(id); record = t2.get('my-record')!; expect(record.enabled).to.be.false; expect(record.content).to.equal(''); await datastore.redo(id); await datastore.redo(id); await datastore.redo(id); record = t2.get('my-record')!; expect(record.enabled).to.be.true; expect(record.content).to.equal('hello, world'); }); it('should be safe to call extra times', async () => { let t2 = datastore.get(schema2); let id = datastore.beginTransaction(); t2.update({ 'my-record': { enabled: true, content: { index: 0, remove: 0, text: 'hello, world' } } }); datastore.endTransaction(); let record = t2.get('my-record')!; expect(record.enabled).to.be.true; expect(record.content).to.equal('hello, world'); await datastore.undo(id); record = t2.get('my-record')!; expect(record.enabled).to.be.false; expect(record.content).to.equal(''); // Try to redo extra times, should not duplicate the patch. await datastore.redo(id); await datastore.redo(id); await datastore.redo(id); record = t2.get('my-record')!; expect(record.enabled).to.be.true; expect(record.content).to.equal('hello, world'); }); it('should emit a changed signal when redoing a change', async () => { let t2 = datastore.get(schema2); let id = datastore.beginTransaction(); t2.update({ 'my-record': { enabled: true } }); datastore.endTransaction(); let record = t2.get('my-record')!; expect(record.enabled).to.be.true; await datastore.undo(id); let called = false; datastore.changed.connect((sender, args) => { expect(args.type).to.equal('redo'); expect(args.transactionId).to.equal(id); called = true; }); await datastore.redo(id); expect(called).to.be.true; }); it('should not emit a changed signal when there is nothing to redo', async () => { let t2 = datastore.get(schema2); let id = datastore.beginTransaction(); t2.update({ 'my-record': { enabled: true } }); datastore.endTransaction(); let record = t2.get('my-record')!; expect(record.enabled).to.be.true; await datastore.undo(id); await datastore.redo(id); let called = false; datastore.changed.connect((sender, args) => { called = true; }); // Already redone, so no signal should be emitted. await datastore.redo(id); expect(called).to.be.false; }); it('should be insensitive to causality', async () => { // Generate transactions that add then delete a value in a map. let t2 = datastore.get(schema2); let id1 = datastore.beginTransaction(); t2.update({ record: { tags: { value: 'tagged' } } }); datastore.endTransaction(); expect(t2.get('record')!.tags.value).to.equal('tagged'); let id2 = datastore.beginTransaction(); t2.update({ record: { tags: { value: null } } }); datastore.endTransaction(); expect(t2.get('record')!.tags.value).to.be.undefined; let transaction1 = adapter.transactions[id1]; let transaction2 = adapter.transactions[id2]; // Design a set of undo and redo operations, after which // transaction1 should be showing, and transaction 2 should not. let operations = ['t1', 't2', 'u1', 'r1', 'u2', 'u1', 'r1', 'u2', 'r2']; // Shuffle the operations many times so that we don't accidentally // get the right answer. for (let i = 0; i < 20; i++) { let ops = shuffle(operations); // Create a new datastore let adapt = new InMemoryServerAdapter(); let store = Datastore.create({ id: 5678, schemas: [schema1, schema2], adapter: adapt }); let table = store.get(schema2); for (let op of ops) { switch (op) { case 't1': adapt.onRemoteTransaction!(transaction1); break; case 't2': adapt.onRemoteTransaction!(transaction2); break; case 'u1': adapt.onUndo!(transaction1); break; case 'u2': adapt.onUndo!(transaction2); break; case 'r1': adapt.onRedo!(transaction1); break; case 'r2': adapt.onRedo!(transaction2); break; default: throw 'Unreachable'; break; } } expect(table.get('record')!.tags.value).to.equal('tagged'); adapt.dispose(); store.dispose(); } }); }); }); }); lumino-2021.12.13/packages/datastore/tests/src/fields.spec.ts000066400000000000000000000055371415564225700236770ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2019, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { expect } from 'chai'; import { Fields, ListField, MapField, RegisterField, TextField } from '@lumino/datastore'; type FieldType = number; let value: FieldType = 11; describe('@lumino/datastore', () => { describe('Fields', () => { describe('Boolean', () => { it('should create a boolean register field', () => { let field = Fields.Boolean(); expect(field).to.be.instanceof(RegisterField); expect(typeof field.value).to.equal('boolean'); }); it('should default to false', () => { let field = Fields.Boolean(); expect(field.value).to.be.false; }); }); describe('Number', () => { it('should create a number register field', () => { let field = Fields.Number(); expect(field).to.be.instanceof(RegisterField); expect(typeof field.value).to.equal('number'); }); it('should default to zero', () => { let field = Fields.Number(); expect(field.value).to.equal(0); }); }); describe('String', () => { it('should create a string register field', () => { let field = Fields.String(); expect(field).to.be.instanceof(RegisterField); expect(typeof field.value).to.equal('string'); }); it('should default to an empty string', () => { let field = Fields.String(); expect(field.value).to.equal(''); }); }); describe('List', () => { it('should create a list field', () => { let field = Fields.List(); expect(field).to.be.instanceof(ListField); }); }); describe('Map', () => { it('should create a map field', () => { let field = Fields.Map(); expect(field).to.be.instanceof(MapField); }); }); describe('Register', () => { it('should create a register field', () => { let field = Fields.Register({ value }); expect(field).to.be.instanceof(RegisterField); expect(typeof field.value).to.equal(typeof value); }); it('should default to the provided initial value', () => { let field = Fields.Register({ value }); expect(field.value).to.equal(value); }); }); describe('Text', () => { it('should create a text field', () => { let field = Fields.Text(); expect(field).to.be.instanceof(TextField); }); }); }); }); lumino-2021.12.13/packages/datastore/tests/src/index.spec.ts000066400000000000000000000011761415564225700235330ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2019, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import './datastore.spec'; import './fields.spec'; import './listfield.spec'; import './mapfield.spec'; import './registerfield.spec'; import './table.spec'; import './textfield.spec'; lumino-2021.12.13/packages/datastore/tests/src/listfield.spec.ts000066400000000000000000000403471415564225700244060ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2019, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { expect } from 'chai'; import { ListField } from '@lumino/datastore'; type ListValue = number; /** * Return a shuffled copy of an array */ function shuffle(array: ReadonlyArray): T[] { let ret = array.slice(); for (let i = ret.length - 1; i > 0; i--) { let j = Math.floor(Math.random() * (i + 1)); // random index from 0 to i [ret[i], ret[j]] = [ret[j], ret[i]]; // swap elements } return ret; } describe('@lumino/datastore', () => { describe('ListField', () => { let field: ListField; beforeEach(() => { field = new ListField({ description: 'A list field storing numbers' }); }); describe('constructor()', () => { it('should create a list field', () => { expect(field).to.be.instanceof(ListField); }); }); describe('type', () => { it('should return the type of the field', () => { expect(field.type).to.equal('list'); }); }); describe('createValue()', () => { it('should create an initial value for the field', () => { let value = field.createValue(); expect(value).to.eql([]); }); }); describe('createMetadata()', () => { it('should create initial metadata for the field', () => { let metadata = field.createMetadata(); expect(metadata.ids).to.eql([]); expect(metadata.cemetery).to.eql({}); }); }); describe('applyUpdate', () => { it('should return the result of the update', () => { let previous = field.createValue(); let metadata = field.createMetadata(); let splice = { index: 0, remove: 0, values: [1, 2, 3] }; let { value, change, patch } = field.applyUpdate({ previous, update: splice, metadata, version: 1, storeId: 1 }); expect(value).to.eql([1, 2, 3]); expect(change[0]).to.eql({ index: 0, removed: [], inserted: [1, 2, 3] }); expect(patch.length).to.equal(1); expect(patch[0].removedValues.length).to.equal(splice.remove); expect(patch[0].insertedValues).to.eql(splice.values); expect(patch[0].removedIds.length).to.equal(splice.remove); expect(patch[0].insertedIds.length).to.equal(splice.values.length); }); it('should accept multiple splices', () => { let previous = field.createValue(); let metadata = field.createMetadata(); let splice1 = { index: 0, remove: 0, values: [1, 2, 3] }; let splice2 = { index: 1, remove: 1, values: [4, 5] }; let { value, change, patch } = field.applyUpdate({ previous, update: [splice1, splice2], metadata, version: 1, storeId: 1 }); expect(value).to.eql([1, 4, 5, 3]); expect(change.length).to.eql(2); expect(change[0]).to.eql({ index: 0, removed: [], inserted: [1, 2, 3] }); expect(change[1]).to.eql({ index: 1, removed: [2], inserted: [4, 5] }); expect(patch.length).to.equal(2); expect(patch[0].removedValues.length).to.equal(splice1.remove); expect(patch[0].insertedValues).to.eql(splice1.values); expect(patch[0].removedIds.length).to.equal(splice1.remove); expect(patch[0].insertedIds.length).to.equal(splice1.values.length); expect(patch[1].removedValues.length).to.equal(splice2.remove); expect(patch[1].insertedValues).to.eql(splice2.values); expect(patch[1].removedIds.length).to.equal(splice2.remove); expect(patch[1].insertedIds.length).to.equal(splice2.values.length); }); it('should accept long splices', () => { let previous = field.createValue(); let values: number[] = []; values.fill(0, 0, 1000000); let metadata = field.createMetadata(); let splice = { index: 0, remove: 0, values }; let { value } = field.applyUpdate({ previous, update: [splice], metadata, version: 1, storeId: 1 }); expect(value).to.eql(values); }); }); describe('applyPatch', () => { it('should return the result of the patch', () => { let previous = field.createValue(); let metadata = field.createMetadata(); // Create a patch let { patch } = field.applyUpdate({ previous, update: { index: 0, remove: 0, values: [1, 2, 3] }, metadata, version: 1, storeId: 1 }); // Reset the metadata metadata = field.createMetadata(); // Apply the patch let patched = field.applyPatch({ previous, metadata, patch }); expect(patched.value).to.eql([1, 2, 3]); expect(patched.change[0]).to.eql({ index: 0, removed: [], inserted: [1, 2, 3] }); }); it('should allow for out-of-order patches', () => { let previous = field.createValue(); let metadata = field.createMetadata(); let firstUpdate = field.applyUpdate({ previous, update: { index: 0, remove: 0, values: [1, 8, 9, 4] }, metadata, version: 1, storeId: 1 }); let secondUpdate = field.applyUpdate({ previous: firstUpdate.value, update: { index: 1, remove: 2, values: [2, 3] }, metadata, version: 2, storeId: 1 }); let thirdUpdate = field.applyUpdate({ previous: secondUpdate.value, update: { index: 4, remove: 0, values: [5, 6, 7] }, metadata, version: 3, storeId: 1 }); expect(thirdUpdate.value).to.eql([1, 2, 3, 4, 5, 6, 7]); // Now if we apply these patches on another client in // a different order, they should give the same result. metadata = field.createMetadata(); let firstPatch = field.applyPatch({ previous, metadata, patch: thirdUpdate.patch }); expect(firstPatch.change[0]).to.eql({ index: 0, removed: [], inserted: [5, 6, 7] }); let secondPatch = field.applyPatch({ previous: firstPatch.value, metadata, patch: secondUpdate.patch }); expect(secondPatch.change[0]).to.eql({ index: 0, removed: [], inserted: [2, 3] }); let thirdPatch = field.applyPatch({ previous: secondPatch.value, metadata, patch: firstUpdate.patch }); expect(thirdPatch.change[0]).to.eql({ index: 2, removed: [], inserted: [4] }); expect(thirdPatch.change[1]).to.eql({ index: 0, removed: [], inserted: [1] }); expect(thirdPatch.value).to.eql([1, 2, 3, 4, 5, 6, 7]); }); it('should allow for racing patches', () => { let current = field.createValue(); let metadata = field.createMetadata(); let values = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; let patches: ListField.Patch[] = []; // Recreate the values array one update at a time, // capturing the patches. for (let i = 0, version = 0; i < values.length; i++) { let u1 = field.applyUpdate({ previous: current, metadata, update: { index: i, remove: 0, values: [values[i], values[i], values[i]] }, version: version, storeId: 1 }); version++; current = u1.value; patches.push(u1.patch); let u2 = field.applyUpdate({ previous: current, metadata, update: { index: i + 1, remove: 2, values: [] }, version: version, storeId: 1 }); version++; current = u2.value; patches.push(u2.patch); } expect(current).to.eql(values); // Shuffle the patches and apply them in a random order to // a new ListField. We try this multiple times to ensure we // don't accidentally get it right. for (let i = 0; i < 10; ++i) { let shuffled = shuffle(patches); current = field.createValue(); metadata = field.createMetadata(); shuffled.forEach(patch => { let { value } = field.applyPatch({ previous: current, metadata, patch }); current = value; }); expect(current).to.eql(values); } }); it('should handle concurrently deleted values', () => { let initial = field.createValue(); let metadata = field.createMetadata(); // First insert some values. let commonUpdate = field.applyUpdate({ previous: initial, metadata, update: { index: 0, remove: 0, values: [1, 2, 3, 4] }, version: 1, storeId: 1 }); // Two collaborators concurrently remove the same value. let metadataA = field.createMetadata(); let patchA = field.applyPatch({ previous: initial, metadata: metadataA, patch: commonUpdate.patch }); let updateA = field.applyUpdate({ previous: patchA.value, metadata: metadataA, update: { index: 1, remove: 2, values: [] }, version: 2, storeId: 2 }); expect(updateA.value).to.eql([1, 4]); let metadataB = field.createMetadata(); let patchB = field.applyPatch({ previous: initial, metadata: metadataB, patch: commonUpdate.patch }); let updateB = field.applyUpdate({ previous: patchB.value, metadata: metadataB, update: { index: 0, remove: 2, values: [] }, version: 2, storeId: 3 }); expect(updateB.value).to.eql([3, 4]); // Apply the A patch to the B collaborator let patchB2 = field.applyPatch({ previous: updateB.value, metadata: metadataB, patch: updateA.patch }); expect(patchB2.value).to.eql([4]); // Apply the B patch to the A collaborator let patchA2 = field.applyPatch({ previous: updateA.value, metadata: metadataA, patch: updateB.patch }); expect(patchA2.value).to.eql([4]); // Apply the patches to a third collaborator out-of-order. let metadataC = field.createMetadata(); let patchC1 = field.applyPatch({ previous: initial, metadata: metadataC, patch: updateB.patch }); let patchC2 = field.applyPatch({ previous: patchC1.value, metadata: metadataC, patch: updateA.patch }); let patchC3 = field.applyPatch({ previous: patchC2.value, metadata: metadataC, patch: commonUpdate.patch }); // Check the final value. expect(patchC3.value).to.eql([4]); }); it('should handle large patches', () => { let previous = field.createValue(); let metadata = field.createMetadata(); let values = new Array(250000).fill(0); // Create a patch let { patch } = field.applyUpdate({ previous, update: { index: 0, remove: 0, values }, metadata, version: 1, storeId: 1 }); // Reset the metadata metadata = field.createMetadata(); // Apply the patch let patched = field.applyPatch({ previous, metadata, patch }); expect(patched.value.length).to.eql(values.length); }); }); describe('unapplyPatch', () => { it('should remove a patch from the history', () => { let previous = field.createValue(); let metadata = field.createMetadata(); // Create a patch let updated1 = field.applyUpdate({ previous, update: { index: 0, remove: 0, values: [1, 2, 3, 9] }, metadata, version: 1, storeId: 1 }); let updated2 = field.applyUpdate({ previous: updated1.value, update: { index: 3, remove: 1, values: [4, 5, 6] }, metadata, version: 1, storeId: 1 }); expect(updated2.value).to.eql([1, 2, 3, 4, 5, 6]); // Reset the metadata and apply the patches metadata = field.createMetadata(); let patched1 = field.applyPatch({ previous, metadata, patch: updated1.patch }); let patched2 = field.applyPatch({ previous: patched1.value, metadata, patch: updated2.patch }); expect(patched2.value).to.eql([1, 2, 3, 4, 5, 6]); // Unapply the patches out of order. let unpatched1 = field.unapplyPatch({ previous: patched2.value, metadata, patch: updated1.patch }); expect(unpatched1.value).to.eql([4, 5, 6]); expect(unpatched1.change[0]).to.eql({ index: 0, removed: [1, 2, 3], inserted: [] }); let unpatched2 = field.unapplyPatch({ previous: unpatched1.value, metadata, patch: updated2.patch }); expect(unpatched2.value).to.eql([]); expect(unpatched2.change[0]).to.eql({ index: 0, removed: [4, 5, 6], inserted: [] }); }); it('should handle concurrently deleted text', () => { let previous = field.createValue(); let metadata = field.createMetadata(); // Create a patch let { patch } = field.applyUpdate({ previous, update: { index: 0, remove: 0, values: [1, 2, 3] }, metadata, version: 1, storeId: 1 }); // Reset the metadata and unnaply the patch before applying it. metadata = field.createMetadata(); let unpatched = field.unapplyPatch({ previous, metadata, patch }); expect(unpatched.value).to.eql([]); expect(unpatched.change.length).to.equal(0); let patched = field.applyPatch({ previous: unpatched.value, metadata, patch }); expect(patched.value).to.eql([]); expect(patched.change.length).to.equal(0); }); }); describe('mergeChange', () => { it('should merge two successive changes', () => { let change1 = [ { index: 0, removed: [], inserted: [0, 1] } ]; let change2 = [ { index: 1, removed: [1], inserted: [2, 3] } ]; let result = field.mergeChange(change1, change2); expect(result).to.eql([...change1, ...change2]); }); }); describe('mergePatch', () => { it('should merge two successive patches', () => { let patch1 = [ { removedIds: [], removedValues: [], insertedIds: ['id-1', 'id-2'], insertedValues: [0, 1] } ]; let patch2 = [ { removedIds: ['id-1'], removedValues: [0], insertedIds: ['id-3', 'id-4'], insertedValues: [2, 3] } ]; let result = field.mergePatch(patch1, patch2); expect(result).to.eql([...patch1, ...patch2]); }); }); }); }); lumino-2021.12.13/packages/datastore/tests/src/mapfield.spec.ts000066400000000000000000000214421415564225700242030ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2019, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { expect } from 'chai'; import { MapField } from '@lumino/datastore'; type MapValue = string; describe('@lumino/datastore', () => { describe('MapField', () => { let field: MapField; beforeEach(() => { field = new MapField({ description: 'A map field storing strings' }); }); describe('constructor()', () => { it('should create a map field', () => { expect(field).to.be.instanceof(MapField); }); }); describe('type', () => { it('should return the type of the field', () => { expect(field.type).to.equal('map'); }); }); describe('createValue()', () => { it('should create an initial value for the field', () => { let value = field.createValue(); expect(value).to.eql({}); }); }); describe('createMetadata()', () => { it('should create initial metadata for the field', () => { let metadata = field.createMetadata(); expect(metadata).to.eql({ ids: {}, values: {} }); }); }); describe('applyUpdate', () => { it('should apply an update to a value', () => { let previous = { zero: 'zeroth' }; let metadata = field.createMetadata(); let update = { one: 'first', two: 'second' }; let { value, patch } = field.applyUpdate({ previous, metadata, update, version: 1, storeId: 1 }); expect(value).to.eql({ zero: 'zeroth', one: 'first', two: 'second' }); expect(patch.values).to.eql(update); }); it('should indicate changed values in the change object', () => { let previous = { zero: 'zeroth', one: 'first' }; let metadata = field.createMetadata(); let update = { one: null, // remove this field. two: 'second' // add this field. }; let { value, change } = field.applyUpdate({ previous, metadata, update, version: 1, storeId: 1 }); expect(value).to.eql({ zero: 'zeroth', two: 'second' }); expect(change.previous).to.eql({ one: 'first', two: null }); expect(change.current).to.eql({ one: null, two: 'second' }); }); }); describe('applyPatch', () => { it('should apply a patch to a value', () => { let previous = { zero: 'zeroth' }; let metadata = field.createMetadata(); let update = field.applyUpdate({ previous, metadata, update: { one: 'first', two: 'second' }, version: 1, storeId: 1 }); metadata = field.createMetadata(); let { value } = field.applyPatch({ previous, patch: update.patch, metadata }); expect(value).to.eql({ zero: 'zeroth', one: 'first', two: 'second' }); }); it('should indicate changed values in the change object', () => { let previous = { zero: 'zeroth', one: 'first' }; let metadata = field.createMetadata(); let update = field.applyUpdate({ previous, metadata, update: { one: null, // remove this field. two: 'second' // add this field. }, version: 1, storeId: 1 }); metadata = field.createMetadata(); let { value, change } = field.applyPatch({ previous, patch: update.patch, metadata }); expect(value).to.eql({ zero: 'zeroth', two: 'second' }); expect(change.previous).to.eql({ one: 'first', two: null }); expect(change.current).to.eql({ one: null, two: 'second' }); }); it('should allow for out-of-order patches', () => { let previous = field.createValue(); let metadata = field.createMetadata(); // Generate some patches. let update1 = field.applyUpdate({ previous, metadata, update: { a: 'a', b: 'b' }, version: 1, storeId: 1 }); let update2 = field.applyUpdate({ previous: update1.value, metadata, update: { a: null, c: 'c' }, version: 2, storeId: 1 }); expect(update2.value).to.eql({ b: 'b', c: 'c' }); //Reset the metadata and apply the patches out of order. metadata = field.createMetadata(); let patch1 = field.applyPatch({ previous, metadata, patch: update2.patch }); expect(patch1.value).to.eql({ c: 'c' }); expect(patch1.change.previous).to.eql({ a: null, c: null }); expect(patch1.change.current).to.eql({ a: null, c: 'c' }); let patch2 = field.applyPatch({ previous: patch1.value, metadata, patch: update1.patch }); expect(patch2.value).to.eql({ b: 'b', c: 'c' }); expect(patch2.change.previous).to.eql({ a: null, b: null }); expect(patch2.change.current).to.eql({ a: null, b: 'b' }); }); }); describe('unapplyPatch', () => { it('should unapply a patch to a map', () => { let metadata = field.createMetadata(); let update = field.applyUpdate({ previous: field.createValue(), metadata, update: { one: 'first', two: 'second' }, version: 1, storeId: 1 }); // Reset the metadata. metadata = field.createMetadata(); let { value } = field.applyPatch({ previous: field.createValue(), patch: update.patch, metadata }); expect(value).to.eql({ one: 'first', two: 'second' }); let unpatched = field.unapplyPatch({ previous: value, patch: update.patch, metadata }); expect(unpatched.value).to.eql(field.createValue()); expect(unpatched.change.previous).to.eql({ one: 'first', two: 'second' }); expect(unpatched.change.current).to.eql({ one: null, two: null }); }); }); describe('mergeChange', () => { it('should merge two successive changes', () => { let change1 = { previous: {}, current: { first: 'first-change' } }; let change2 = { previous: {}, current: { second: 'second-change' } }; let result = field.mergeChange(change1, change2); expect(result.previous).to.eql({}); expect(result.current).to.eql({ first: 'first-change', second: 'second-change' }); }); it('should prefer the first change for the previous merged field', () => { let change1 = { previous: { value: 'value' }, current: { value: 'value-changed' } }; let change2 = { previous: { value: 'other' }, current: { value: 'other-changed' } }; let result = field.mergeChange(change1, change2); expect(result.previous).to.eql({ value: 'value' }); }); it('should prefer the second change for the current merged field', () => { let change1 = { previous: { value: 'value' }, current: { value: 'value-changed' } }; let change2 = { previous: { value: 'other' }, current: { value: 'other-changed' } }; let result = field.mergeChange(change1, change2); expect(result.current).to.eql({ value: 'other-changed' }); }); }); describe('mergePatch', () => { it('should merge two patches into a single patch object', () => { let patch1 = { id: 'one', values: { first: 'first' } }; let patch2 = { id: 'two', values: { second: 'second' } }; let result = field.mergePatch(patch1, patch2); expect(result).to.eql({ id: 'two', values: { first: 'first', second: 'second' } }); }); it('should prefer the second patch over the first', () => { let patch1 = { id: 'one', values: { first: 'first', second: 'second' } }; let patch2 = { id: 'two', values: { first: 'other', second: 'next-other' } }; let result = field.mergePatch(patch1, patch2); expect(result).to.eql(patch2); }); }); }); }); lumino-2021.12.13/packages/datastore/tests/src/registerfield.spec.ts000066400000000000000000000160511415564225700252520ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2019, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { expect } from 'chai'; import { RegisterField } from '@lumino/datastore'; type RegisterValue = { value: string }; describe('@lumino/datastore', () => { describe('RegisterField', () => { let field: RegisterField; beforeEach(() => { field = new RegisterField({ value: { value: 'a' } }); }); describe('constructor()', () => { it('should accept options', () => { expect(field.value.value).to.equal('a'); }); }); describe('type', () => { it('should return the type of the field', () => { expect(field.type).to.equal('register'); }); }); describe('createValue()', () => { it('should create an initial value for the field', () => { let value = field.createValue(); expect(value.value).to.equal('a'); }); }); describe('createMetadata()', () => { it('should create initial metadata for the field', () => { let metadata = field.createMetadata(); expect(metadata.ids.length).to.equal(0); expect(metadata.values.length).to.equal(0); }); }); describe('applyUpdate', () => { it('should return the result of the update', () => { let newValue = { value: 'updated' }; let update = { previous: field.value, metadata: field.createMetadata(), update: newValue, version: 2, storeId: 1 }; let result = field.applyUpdate(update); let { value, change, patch } = result; expect(value.value).to.equal(newValue.value); expect(change.previous).to.eql(field.value); expect(change.current).to.eql(value); expect(patch.id).to.not.equal(''); expect(patch.value).to.eql(newValue); }); }); describe('applyPatch', () => { it('should return the result of the patch', () => { let metadata = field.createMetadata(); let update = field.applyUpdate({ previous: field.value, metadata, update: { value: 'updated' }, version: 1, storeId: 1 }); expect(update.value.value).to.equal('updated'); metadata = field.createMetadata(); let patch = field.applyPatch({ previous: field.value, metadata, patch: update.patch }); expect(patch.value.value).to.equal('updated'); expect(patch.change.current.value).to.equal('updated'); expect(patch.change.previous.value).to.equal('a'); }); it('should allow for out-of-order patches', () => { let metadata = field.createMetadata(); // Apply updates to generate patches. let update1 = field.applyUpdate({ previous: field.value, metadata, update: { value: 'updated' }, version: 1, storeId: 1 }); let update2 = field.applyUpdate({ previous: update1.value, metadata, update: { value: 'updated-later' }, version: 2, storeId: 1 }); expect(update2.value.value).to.equal('updated-later'); // Apply the patches out of order. metadata = field.createMetadata(); let patch1 = field.applyPatch({ previous: field.value, metadata, patch: update2.patch }); let patch2 = field.applyPatch({ previous: patch1.value, metadata, patch: update1.patch }); expect(patch2.value.value).to.equal('updated-later'); expect(patch2.change.current.value).to.equal('updated-later'); expect(patch2.change.previous.value).to.equal('updated-later'); }); }); describe('unapplyPatch', () => { it('should remove a patch from a field', () => { // Create the patch let metadata = field.createMetadata(); let update1 = field.applyUpdate({ previous: field.value, metadata, update: { value: 'updated' }, version: 1, storeId: 1 }); let update2 = field.applyUpdate({ previous: update1.value, metadata, update: { value: 'updated-later' }, version: 2, storeId: 1 }); expect(update1.value.value).to.equal('updated'); expect(update2.value.value).to.equal('updated-later'); // Reset the metadata and apply the patches. metadata = field.createMetadata(); let patched1 = field.applyPatch({ previous: field.value, metadata, patch: update1.patch }); let patched2 = field.applyPatch({ previous: patched1.value, metadata, patch: update2.patch }); expect(patched2.value.value).to.equal('updated-later'); expect(patched2.change.current.value).to.equal('updated-later'); expect(patched2.change.previous.value).to.equal('updated'); // Unapply the patches one at a time. let unpatched1 = field.unapplyPatch({ previous: patched2.value, metadata, patch: update2.patch }); expect(unpatched1.value.value).to.equal('updated'); expect(unpatched1.change.current.value).to.equal('updated'); expect(unpatched1.change.previous.value).to.equal('updated-later'); let unpatched2 = field.unapplyPatch({ previous: unpatched1.value, metadata, patch: update1.patch }); // If all the patches have been unapplied, revert to the initial value. expect(unpatched2.value.value).to.equal('a'); expect(unpatched2.change.current.value).to.equal('a'); expect(unpatched2.change.previous.value).to.equal('updated'); }); }); describe('mergeChange', () => { it('should merge two successive changes', () => { let change1 = { previous: field.value, current: { value: 'first-change' } }; let change2 = { previous: { value: 'first-change' }, current: { value: 'second-change' } }; let merged = field.mergeChange(change1, change2); expect(merged.previous.value).to.equal('a'); expect(merged.current.value).to.equal('second-change'); }); }); describe('mergePatch', () => { it('should merge two successive patches by choosing the second', () => { let patch1 = { id: 'first', value: { value: 'first' } }; let patch2 = { id: 'second', value: { value: 'second' } }; let merged = field.mergePatch(patch1, patch2); expect(merged).to.eql(patch2); }); }); }); }); lumino-2021.12.13/packages/datastore/tests/src/table.spec.ts000066400000000000000000000211501415564225700235050ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2019, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { expect } from 'chai'; import { toArray } from '@lumino/algorithm'; import { Datastore, Fields, ListField, RegisterField, Table, TextField } from '@lumino/datastore'; type CustomMetadata = { id: string }; type TestSchema = { id: string; fields: { content: TextField; count: RegisterField; enabled: RegisterField; links: ListField; metadata: RegisterField; }; }; let schema: TestSchema = { id: 'test-schema', fields: { content: Fields.Text(), count: Fields.Number(), enabled: Fields.Boolean(), links: Fields.List(), metadata: Fields.Register({ value: { id: 'identifier' } }) } }; /** * Remove readonly guards from Context for testing purposes. */ type MutableContext = { -readonly [K in keyof Datastore.Context]: Datastore.Context[K]; }; describe('@lumino/datastore', () => { describe('Table', () => { let table: Table; let context: MutableContext; beforeEach(() => { context = { inTransaction: false, transactionId: '', version: 1, storeId: 1, change: {}, patch: {} }; table = Table.create(schema, context); }); describe('create()', () => { it('should create a new table', () => { let table = Table.create(schema, context); expect(table).to.be.instanceof(Table); }); }); describe('schema', () => { it('should be the schema for the table', () => { expect(table.schema).to.equal(schema); }); }); describe('isEmpty', () => { it('should return whether the table is empty', () => { expect(table.isEmpty).to.be.true; context.inTransaction = true; table.update({ 'my-record': { enabled: true, count: 1 } }); context.inTransaction = false; expect(table.isEmpty).to.be.false; }); }); describe('size', () => { it('should return the size of the table', () => { expect(table.size).to.equal(0); context.inTransaction = true; table.update({ 'my-record': { enabled: true, count: 1 }, 'my-other-record': { enabled: false, count: 2 }, 'my-other-other-record': { enabled: false, count: 3 } }); context.inTransaction = false; expect(table.size).to.equal(3); }); }); describe('iter()', () => { it('should return an iterator over the records in the table', () => { context.inTransaction = true; table.update({ 'my-record': {}, 'my-other-record': {}, 'my-other-other-record': {} }); context.inTransaction = false; let arr = toArray(table.iter()); expect(arr.length).to.equal(3); }); }); describe('has()', () => { it('should return whether the table has a given record', () => { expect(table.has('my-record')).to.be.false; context.inTransaction = true; table.update({ 'my-record': { enabled: true, count: 1 } }); context.inTransaction = false; expect(table.has('my-record')).to.be.true; }); }); describe('get()', () => { it('should return undefined if the record does not exist', () => { expect(table.get('my-record')).to.be.undefined; }); it('should get an existing record from a table', () => { context.inTransaction = true; table.update({ 'my-record': { enabled: true, count: 1 } }); context.inTransaction = false; let record = table.get('my-record')!; expect(record.$id).to.equal('my-record'); expect(record.enabled).to.be.true; expect(record.count).to.equal(1); }); }); describe('update()', () => { it('should raise if the table is not in a transaction', () => { expect(() => { table.update({ 'my-record': { enabled: true, count: 1 } }); }).to.throw('A table can only be updated during a transaction'); }); it('should create a record if it does not exist', () => { context.inTransaction = true; table.update({ 'my-record': { enabled: true, count: 1 } }); context.inTransaction = false; let record = table.get('my-record')!; expect(record.$id).to.equal('my-record'); expect(record['@@metadata']).to.not.equal(undefined); }); it('should initialize values appropriately', () => { context.inTransaction = true; table.update({ 'my-record': {} }); context.inTransaction = false; let record = table.get('my-record')!; expect(record.content).to.equal(''); expect(record.enabled).to.be.false; expect(record.count).to.equal(0); expect(record.links).to.eql([]); expect(record.metadata).to.eql({ id: 'identifier' }); }); it('should update the records in the table', () => { context.inTransaction = true; table.update({ 'my-record': {} }); context.inTransaction = false; let record = table.get('my-record')!; expect(record.content).to.equal(''); context.inTransaction = true; table.update({ 'my-record': { content: { index: 0, remove: 0, text: 'text' }, links: { index: 0, remove: 0, values: ['a', 'b', 'c'] }, enabled: true, count: 10, metadata: { id: 'new-identifier' } } }); context.inTransaction = false; record = table.get('my-record')!; expect(record.content).to.equal('text'); expect(record.enabled).to.be.true; expect(record.count).to.equal(10); expect(record.links).to.eql(['a', 'b', 'c']); expect(record.metadata).to.eql({ id: 'new-identifier' }); }); }); describe('patch()', () => { it('should create a record if it does not exist', () => { expect(table.get('my-record')).to.be.undefined; let patch = { 'my-record': { count: { id: 'unique-id', value: 2 } } }; Table.patch(table, patch); expect(table.get('my-record')).to.not.be.undefined; }); it('should patch an existing record', () => { context.inTransaction = true; table.update({ 'my-record': { count: 1 } }); context.inTransaction = false; expect(table.get('my-record')!.count).to.equal(1); let patch = { 'my-record': { count: { id: 'unique-id', value: 2 } } }; Table.patch(table, patch); expect(table.get('my-record')!.count).to.equal(2); }); it('should return a user-facing change for the patch', () => { let patch = { 'my-record': { enabled: { id: 'unique-id', value: true } } }; let change = Table.patch(table, patch); expect(change['my-record']!.enabled!.previous).to.be.false; expect(change['my-record']!.enabled!.current).to.be.true; }); }); describe('unpatch()', () => { it('should create a record if it does not exist', () => { expect(table.get('my-record')).to.be.undefined; let patch = { 'my-record': { count: { id: 'unique-id', value: 2 } } }; Table.unpatch(table, patch); expect(table.get('my-record')).to.not.be.undefined; }); it('should patch an existing record', () => { context.inTransaction = true; table.update({ 'my-record': { count: 1 } }); context.inTransaction = false; expect(table.get('my-record')!.count).to.equal(1); let patch = { 'my-record': { count: { id: 'unique-id', value: 2 } } }; Table.patch(table, patch); Table.unpatch(table, patch); expect(table.get('my-record')!.count).to.equal(1); }); it('should return a user-facing change for the patch', () => { let patch = { 'my-record': { enabled: { id: 'unique-id', value: true } } }; Table.patch(table, patch); let change = Table.unpatch(table, patch); expect(change['my-record']!.enabled!.previous).to.be.true; expect(change['my-record']!.enabled!.current).to.be.false; }); }); }); }); lumino-2021.12.13/packages/datastore/tests/src/textfield.spec.ts000066400000000000000000000361471415564225700244220ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2019, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { expect } from 'chai'; import { TextField } from '@lumino/datastore'; /** * Return a shuffled copy of an array */ function shuffle(array: ReadonlyArray): T[] { let ret = array.slice(); for (let i = ret.length - 1; i > 0; i--) { let j = Math.floor(Math.random() * (i + 1)); // random index from 0 to i [ret[i], ret[j]] = [ret[j], ret[i]]; // swap elements } return ret; } describe('@lumino/datastore', () => { describe('TextField', () => { let field: TextField; beforeEach(() => { field = new TextField({ description: 'A text field storing strings' }); }); describe('constructor()', () => { it('should create a list field', () => { expect(field).to.be.instanceof(TextField); }); }); describe('type', () => { it('should return the type of the field', () => { expect(field.type).to.equal('text'); }); }); describe('createValue()', () => { it('should create an initial value for the field', () => { expect(field.createValue()).to.equal(''); }); }); describe('createMetadata()', () => { it('should create initial metadata for the field', () => { expect(field.createMetadata()).to.eql({ ids: [], cemetery: {} }); }); }); describe('applyUpdate', () => { it('should return the result of the update', () => { let previous = field.createValue(); let metadata = field.createMetadata(); let splice = { index: 0, remove: 0, text: 'abc' }; let { value, change, patch } = field.applyUpdate({ previous, update: splice, metadata, version: 1, storeId: 1 }); expect(value).to.equal('abc'); expect(change[0]).to.eql({ index: 0, removed: '', inserted: 'abc' }); expect(patch.length).to.equal(1); expect(patch[0].removedText.length).to.equal(splice.remove); expect(patch[0].insertedText).to.equal(splice.text); expect(patch[0].removedIds.length).to.equal(splice.remove); expect(patch[0].insertedIds.length).to.equal(splice.text.length); }); it('should accept multiple splices', () => { let previous = field.createValue(); let metadata = field.createMetadata(); let splice1 = { index: 0, remove: 0, text: 'abc' }; let splice2 = { index: 1, remove: 1, text: 'de' }; let { value, change, patch } = field.applyUpdate({ previous, update: [splice1, splice2], metadata, version: 1, storeId: 1 }); expect(value).to.equal('adec'); expect(change.length).to.eql(2); expect(change[0]).to.eql({ index: 0, removed: '', inserted: 'abc' }); expect(change[1]).to.eql({ index: 1, removed: 'b', inserted: 'de' }); expect(patch.length).to.equal(2); expect(patch[0].removedText.length).to.equal(splice1.remove); expect(patch[0].insertedText).to.eql(splice1.text); expect(patch[0].removedIds.length).to.equal(splice1.remove); expect(patch[0].insertedIds.length).to.equal(splice1.text.length); expect(patch[1].removedText.length).to.equal(splice2.remove); expect(patch[1].insertedText).to.equal(splice2.text); expect(patch[1].removedIds.length).to.equal(splice2.remove); expect(patch[1].insertedIds.length).to.equal(splice2.text.length); }); it('should accept long splices', () => { let previous = field.createValue(); let text = 'a'.repeat(1000000); let metadata = field.createMetadata(); let splice = { index: 0, remove: 0, text }; let { value } = field.applyUpdate({ previous, update: [splice], metadata, version: 1, storeId: 1 }); expect(value).to.equal(text); }); }); describe('applyPatch', () => { it('should return the result of the patch', () => { let previous = field.createValue(); let metadata = field.createMetadata(); // Create a patch let { patch } = field.applyUpdate({ previous, update: { index: 0, remove: 0, text: 'abc' }, metadata, version: 1, storeId: 1 }); // Reset the metadata metadata = field.createMetadata(); let patched = field.applyPatch({ previous, metadata, patch }); expect(patched.value).to.equal('abc'); expect(patched.change[0]).to.eql({ index: 0, removed: '', inserted: 'abc' }); }); it('should allow for out-of-order patches', () => { let previous = field.createValue(); let metadata = field.createMetadata(); // Generate some patches. let firstUpdate = field.applyUpdate({ previous, update: { index: 0, remove: 0, text: 'agc' }, metadata, version: 1, storeId: 1 }); let secondUpdate = field.applyUpdate({ previous: firstUpdate.value, update: { index: 1, remove: 1, text: 'b' }, metadata, version: 2, storeId: 1 }); let thirdUpdate = field.applyUpdate({ previous: secondUpdate.value, update: { index: 3, remove: 0, text: 'def' }, metadata, version: 3, storeId: 1 }); expect(thirdUpdate.value).to.equal('abcdef'); // Now apply the patches on another client in a different order. // They should have the same resulting value. metadata = field.createMetadata(); let firstPatch = field.applyPatch({ previous, metadata, patch: thirdUpdate.patch }); expect(firstPatch.change[0]).to.eql({ index: 0, removed: '', inserted: 'def' }); let secondPatch = field.applyPatch({ previous: firstPatch.value, metadata, patch: secondUpdate.patch }); expect(secondPatch.change[0]).to.eql({ index: 0, removed: '', inserted: 'b' }); let thirdPatch = field.applyPatch({ previous: secondPatch.value, metadata, patch: firstUpdate.patch }); expect(thirdPatch.change[0]).to.eql({ index: 1, removed: '', inserted: 'c' }); expect(thirdPatch.change[1]).to.eql({ index: 0, removed: '', inserted: 'a' }); expect(thirdPatch.value).to.equal('abcdef'); }); it('should allow for racing patches', () => { let current = field.createValue(); let metadata = field.createMetadata(); let values = 'abcdefghijk'; let patches: TextField.Patch[] = []; // Recreate the values array one update at a time, // capturing the patches. for (let i = 0, version = 0; i < values.length; i++) { let u1 = field.applyUpdate({ previous: current, metadata, update: { index: i, remove: 0, text: values[i] + values[i] + values[i] }, version, storeId: 1 }); version++; current = u1.value; patches.push(u1.patch); let u2 = field.applyUpdate({ previous: current, metadata, update: { index: i + 1, remove: 2, text: '' }, version, storeId: 1 }); version++; current = u2.value; patches.push(u2.patch); } expect(current).to.eql(values); // Shuffle the patches and apply them in a random order to // a new ListField. We try this multiple times to ensure we // don't accidentally get it right. for (let i = 0; i < 10; ++i) { let shuffled = shuffle(patches); current = field.createValue(); metadata = field.createMetadata(); shuffled.forEach(patch => { let { value } = field.applyPatch({ previous: current, metadata, patch }); current = value; }); expect(current).to.eql(values); } }); it('should handle concurrently deleted values', () => { let initial = field.createValue(); let metadata = field.createMetadata(); // First insert some values. let commonUpdate = field.applyUpdate({ previous: initial, metadata, update: { index: 0, remove: 0, text: 'abcd' }, version: 1, storeId: 1 }); // Two collaborators concurrently remove the same value. let metadataA = field.createMetadata(); let patchA = field.applyPatch({ previous: initial, metadata: metadataA, patch: commonUpdate.patch }); let updateA = field.applyUpdate({ previous: patchA.value, metadata: metadataA, update: { index: 1, remove: 2, text: '' }, version: 2, storeId: 2 }); expect(updateA.value).to.eql('ad'); let metadataB = field.createMetadata(); let patchB = field.applyPatch({ previous: initial, metadata: metadataB, patch: commonUpdate.patch }); let updateB = field.applyUpdate({ previous: patchB.value, metadata: metadataB, update: { index: 0, remove: 2, text: '' }, version: 2, storeId: 3 }); expect(updateB.value).to.eql('cd'); // Apply the A patch to the B collaborator let patchB2 = field.applyPatch({ previous: updateB.value, metadata: metadataB, patch: updateA.patch }); expect(patchB2.value).to.eql('d'); // Apply the B patch to the A collaborator let patchA2 = field.applyPatch({ previous: updateA.value, metadata: metadataA, patch: updateB.patch }); expect(patchA2.value).to.eql('d'); // Apply the patches to a third collaborator out-of-order. let metadataC = field.createMetadata(); let patchC1 = field.applyPatch({ previous: initial, metadata: metadataC, patch: updateB.patch }); let patchC2 = field.applyPatch({ previous: patchC1.value, metadata: metadataC, patch: updateA.patch }); let patchC3 = field.applyPatch({ previous: patchC2.value, metadata: metadataC, patch: commonUpdate.patch }); // Check the final value. expect(patchC3.value).to.eql('d'); }); }); describe('unapplyPatch', () => { it('should remove a patch from the history', () => { let previous = field.createValue(); let metadata = field.createMetadata(); // Create a patch let updated1 = field.applyUpdate({ previous, update: { index: 0, remove: 0, text: 'abcp' }, metadata, version: 1, storeId: 1 }); let updated2 = field.applyUpdate({ previous: updated1.value, update: { index: 3, remove: 1, text: 'def' }, metadata, version: 2, storeId: 1 }); expect(updated2.value).to.equal('abcdef'); // Reset the metadata and apply the patches metadata = field.createMetadata(); let patched1 = field.applyPatch({ previous, metadata, patch: updated1.patch }); let patched2 = field.applyPatch({ previous: patched1.value, metadata, patch: updated2.patch }); expect(patched2.value).to.equal('abcdef'); // Unapply the patches out of order. let unpatched1 = field.unapplyPatch({ previous: patched2.value, metadata, patch: updated1.patch }); expect(unpatched1.value).to.equal('def'); expect(unpatched1.change[0]).to.eql({ index: 0, removed: 'abc', inserted: '' }); let unpatched2 = field.unapplyPatch({ previous: unpatched1.value, metadata, patch: updated2.patch }); expect(unpatched2.value).to.equal(''); expect(unpatched2.change[0]).to.eql({ index: 0, removed: 'def', inserted: '' }); }); it('should handle concurrently deleted text', () => { let previous = field.createValue(); let metadata = field.createMetadata(); // Create a patch let { patch } = field.applyUpdate({ previous, update: { index: 0, remove: 0, text: 'abc' }, metadata, version: 1, storeId: 1 }); // Reset the metadata and unnaply the patch before applying it. metadata = field.createMetadata(); let unpatched = field.unapplyPatch({ previous, metadata, patch }); expect(unpatched.value).to.equal(''); expect(unpatched.change.length).to.equal(0); let patched = field.applyPatch({ previous: unpatched.value, metadata, patch }); expect(patched.value).to.equal(''); expect(patched.change.length).to.equal(0); }); }); describe('mergeChange', () => { it('should merge two successive changes', () => { let change1 = [ { index: 0, removed: '', inserted: 'ab' } ]; let change2 = [ { index: 1, removed: 'b', inserted: 'cd' } ]; let result = field.mergeChange(change1, change2); expect(result).to.eql([...change1, ...change2]); }); }); describe('mergePatch', () => { it('should merge two successive patches', () => { let patch1 = [ { removedIds: [], removedText: '', insertedIds: ['id-1', 'id-2'], insertedText: 'ab' } ]; let patch2 = [ { removedIds: ['id-2'], removedText: 'b', insertedIds: ['id-3', 'id-4'], insertedText: 'cd' } ]; let result = field.mergePatch(patch1, patch2); expect(result).to.eql([...patch1, ...patch2]); }); }); }); }); lumino-2021.12.13/packages/datastore/tests/tsconfig.json000066400000000000000000000005431415564225700230400ustar00rootroot00000000000000{ "compilerOptions": { "declaration": false, "noImplicitAny": true, "noEmitOnError": true, "noUnusedLocals": true, "strictNullChecks": true, "module": "commonjs", "moduleResolution": "node", "target": "ES5", "outDir": "./build", "lib": ["ES6", "DOM"], "types": ["chai", "mocha"] }, "include": ["src/*"] } lumino-2021.12.13/packages/datastore/tests/webpack.config.js000066400000000000000000000003061415564225700235440ustar00rootroot00000000000000var path = require('path'); module.exports = { entry: './build/index.spec.js', mode: 'development', output: { filename: './build/bundle.test.js', path: path.resolve(__dirname) } }; lumino-2021.12.13/packages/datastore/tsconfig.json000066400000000000000000000010461415564225700216750ustar00rootroot00000000000000{ "compilerOptions": { "composite": true, "sourceMap": true, "declaration": true, "declarationDir": "./types", "declarationMap": true, "noImplicitAny": true, "noEmitOnError": true, "noUnusedLocals": true, "strictNullChecks": true, "module": "ES6", "moduleResolution": "node", "target": "ES5", "outDir": "lib", "rootDir": "src", "lib": ["dom", "es6"], "importHelpers": true, "types": [] }, "include": ["src/*"], "references": [ { "path": "../signaling" } ] } lumino-2021.12.13/packages/default-theme/000077500000000000000000000000001415564225700177235ustar00rootroot00000000000000lumino-2021.12.13/packages/default-theme/images/000077500000000000000000000000001415564225700211705ustar00rootroot00000000000000lumino-2021.12.13/packages/default-theme/images/caretdown.png000066400000000000000000000003221415564225700236610ustar00rootroot00000000000000‰PNG  IHDR rëä|sRGB®ÎégAMA± üa pHYsÃÃÇo¨dtEXtSoftwarepaint.net 4.0.5e…2eCIDAT(S•Ë1 0CQoíñS2•FC‡7ø1à›ŒŽŒŽŒŽŒN;2“û¯È ¨eÐÊ4 wˆ9ï…š-%ôIEND®B`‚lumino-2021.12.13/packages/default-theme/images/caretleft.png000066400000000000000000000003221415564225700236440ustar00rootroot00000000000000‰PNG  IHDR rëä|sRGB®ÎégAMA± üa pHYsÃÃÇo¨dtEXtSoftwarepaint.net 4.0.5e…2eCIDAT(SËA 0AíóÓæ Lkõ0kÚäY‘gEžÁÝ÷ä?A+Šà;:ƒïˆF"E7ò¬Èó ¶Ã[ï…ÇÂb’IEND®B`‚lumino-2021.12.13/packages/default-theme/images/caretright.png000066400000000000000000000003311415564225700240270ustar00rootroot00000000000000‰PNG  IHDR rëä|sRGB®ÎégAMA± üa pHYsÃÃÇo¨dtEXtSoftwarepaint.net 4.0.5e…2eJIDAT(S•ËA À@AíóMæ ìbg$‡Bl˜¨ªß0n0n0fæ{fo5rCŒ=úŽ çˆ†×ÓÜ@F7Œn 77½Š¹Ëï…n1jIEND®B`‚lumino-2021.12.13/packages/default-theme/images/caretup.png000066400000000000000000000003261415564225700233420ustar00rootroot00000000000000‰PNG  IHDR rëä|sRGB®ÎégAMA± üa pHYsÃÃÇo¨dtEXtSoftwarepaint.net 4.0.5e…2eGIDAT(S•ËA DÑÞºÇ/]H”ϰxŸ±ˆø†QÁ¨`T0&wï¿aÌÁ°¾¥-̃Ӱ\h0ÌÿÊèF£‚QÁxÖýíï…( IEND®B`‚lumino-2021.12.13/packages/default-theme/package.json000066400000000000000000000016511415564225700222140ustar00rootroot00000000000000{ "name": "@lumino/default-theme", "version": "0.20.2", "description": "Lumino Default Theme", "homepage": "https://github.com/jupyterlab/lumino", "bugs": { "url": "https://github.com/jupyterlab/lumino/issues" }, "license": "BSD-3-Clause", "author": "S. Chris Colbert ", "contributors": [ "A. Darian ", "Dave Willmer ", "S. Chris Colbert ", "Steven Silvester " ], "files": [ "dist/*", "src/*", "types/*", "images/*.png", "style/*.css", "style/index.js" ], "style": "style/index.css", "styleModule": "style/index.js", "repository": { "type": "git", "url": "https://github.com/jupyterlab/lumino.git" }, "dependencies": { "@lumino/dragdrop": "^1.13.1", "@lumino/widgets": "^1.30.1" }, "publishConfig": { "access": "public" } } lumino-2021.12.13/packages/default-theme/style/000077500000000000000000000000001415564225700210635ustar00rootroot00000000000000lumino-2021.12.13/packages/default-theme/style/accordionpanel.css000066400000000000000000000021711415564225700245570ustar00rootroot00000000000000.lm-AccordionPanel .lm-AccordionPanel-title { box-sizing: border-box; padding: 0px 10px; background: #e5e5e5; border: 1px solid #c0c0c0; border-bottom: none; font: 12px Helvetica, Arial, sans-serif; min-height: 22px; max-height: 22px; min-width: 35px; line-height: 20px; margin: 0px; } .lm-AccordionPanel .lm-AccordionPanel-title:focus, .lm-AccordionPanel .lm-AccordionPanel-title:hover { background: #dbdbdb; } .lm-AccordionPanel .lm-AccordionPanel-title:focus, .lm-AccordionPanel .lm-AccordionPanel-title:last-of-type:focus:not(.lm-mod-expanded) { border: 1px solid lightskyblue; } .lm-AccordionPanel .lm-AccordionPanel-title:last-of-type:not(.lm-mod-expanded) { border-bottom: 1px solid #c0c0c0; } .lm-AccordionPanel .lm-AccordionPanel-title.lm-mod-expanded .lm-AccordionPanel-titleCollapser:before { content: '\f0d8'; font-family: FontAwesome; } .lm-AccordionPanel .lm-AccordionPanel-title .lm-AccordionPanel-titleCollapser:before { content: '\f0d7'; font-family: FontAwesome; position: absolute; right: 10px; } .lm-AccordionPanel .lm-AccordionPanel-titleLabel { padding: 0px 5px; } lumino-2021.12.13/packages/default-theme/style/commandpalette.css000066400000000000000000000057441415564225700246040ustar00rootroot00000000000000/*----------------------------------------------------------------------------- | Copyright (c) Jupyter Development Team. | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ /* */ .p-CommandPalette, /* */ .lm-CommandPalette { font-family: sans-serif; background: #f5f5f5; } /* */ .p-CommandPalette-search, /* */ .lm-CommandPalette-search { padding: 8px; } /* */ .p-CommandPalette-wrapper, /* */ .lm-CommandPalette-wrapper { padding: 4px 6px; background: white; border: 1px solid #e0e0e0; position: relative; } /* */ .p-CommandPalette-input, /* */ .lm-CommandPalette-input { width: 92%; border: none; outline: none; font-size: 16px; } /* */ .p-CommandPalette-header, /* */ .lm-CommandPalette-header { padding: 4px; color: #757575; font-size: 12px; font-weight: 600; background: #e1e1e1; cursor: pointer; } /* */ .p-CommandPalette-header:hover::before, /* */ .lm-CommandPalette-header:hover::before { content: '\2026'; /* ellipsis */ float: right; margin-right: 4px; } /* */ .p-CommandPalette-header > mark, /* */ .lm-CommandPalette-header > mark { background-color: transparent; font-weight: bold; } /* */ .p-CommandPalette-item, /* */ .lm-CommandPalette-item { padding: 4px 8px; color: #757575; font-size: 13px; font-weight: 500; } /* */ .p-CommandPalette-emptyMessage, /* */ .lm-CommandPalette-emptyMessage { padding: 4px; color: #757575; font-size: 12px; font-weight: 600; text-align: center; } /* */ .p-CommandPalette-item.p-mod-disabled, /* */ .lm-CommandPalette-item.lm-mod-disabled { color: rgba(0, 0, 0, 0.25); } /* */ .p-CommandPalette-item.p-mod-active, /* */ .lm-CommandPalette-item.lm-mod-active { background: #7fdbff; } /* */ .p-CommandPalette-item:hover:not(.p-mod-active):not(.p-mod-disabled), /* */ .lm-CommandPalette-item:hover:not(.lm-mod-active):not(.lm-mod-disabled) { background: #e5e5e5; } /* */ .p-CommandPalette-itemIcon, /* */ .lm-CommandPalette-itemIcon { display: none; } /* */ .p-CommandPalette-itemLabel > mark, /* */ .lm-CommandPalette-itemLabel > mark { background-color: transparent; font-weight: bold; } /* */ .p-CommandPalette-item.p-mod-disabled mark, /* */ .lm-CommandPalette-item.lm-mod-disabled mark { color: rgba(0, 0, 0, 0.4); } /* */ .p-CommandPalette-itemCaption, /* */ .lm-CommandPalette-itemCaption { color: #9e9e9e; font-size: 11px; font-weight: 400; } lumino-2021.12.13/packages/default-theme/style/datagrid.css000066400000000000000000000040031415564225700233510ustar00rootroot00000000000000/*----------------------------------------------------------------------------- | Copyright (c) 2014-2018, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ /* */ .p-DataGrid, /* */ .lm-DataGrid { min-width: 64px; min-height: 64px; border: 1px solid #a0a0a0; } /* */ .p-DataGrid-scrollCorner, /* */ .lm-DataGrid-scrollCorner { background-color: #f0f0f0; } /* */ .p-DataGrid-scrollCorner::after, /* */ .lm-DataGrid-scrollCorner::after { content: ''; position: absolute; top: 0; left: 0; width: 1px; height: 1px; background-color: #a0a0a0; } .lm-DataGrid-cellEditorOccluder { pointer-events: none; position: absolute; overflow: hidden; } .lm-DataGrid-cellEditorContainer { pointer-events: auto; position: absolute; background-color: #ffffff; box-sizing: border-box; box-shadow: 0px 0px 6px #006bf7; border: 2px solid #006bf7; } .lm-DataGrid-cellEditorContainer.lm-mod-invalid { box-shadow: 0px 0px 6px red; border: 2px solid red; } .lm-DataGrid-cellEditorContainer > form { width: 100%; height: 100%; overflow: hidden; } .lm-DataGrid-cellEditorWidget { width: 100%; height: 100%; outline: none; box-sizing: border-box; } .lm-DataGrid-cellEditorInput { background-color: #ffffff; border: 0; } .lm-DataGrid-cellEditorCheckbox { margin: 0; } .lm-DataGrid-notification { position: absolute; display: flex; overflow: visible; animation: fade-in 300ms ease-out; } .lm-DataGrid-notificationContainer { box-shadow: 0px 2px 5px #999999; border-radius: 3px; background-color: white; color: black; border: 1px solid black; font-family: sans-serif; font-size: 13px; padding: 4px; } @keyframes fade-in { 0% { opacity: 0; } 50% { opacity: 0.7; } 100% { opacity: 1; } } lumino-2021.12.13/packages/default-theme/style/dockpanel.css000066400000000000000000000012261415564225700235360ustar00rootroot00000000000000/*----------------------------------------------------------------------------- | Copyright (c) Jupyter Development Team. | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ /* */ .p-DockPanel-overlay, /* */ .lm-DockPanel-overlay { background: rgba(255, 255, 255, 0.6); border: 1px dashed black; transition-property: top, left, right, bottom; transition-duration: 150ms; transition-timing-function: ease; } lumino-2021.12.13/packages/default-theme/style/index.css000066400000000000000000000012151415564225700227030ustar00rootroot00000000000000/*----------------------------------------------------------------------------- | Copyright (c) 2014-2018, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ @import '~@lumino/dragdrop/style/index.css'; @import '~@lumino/widgets/style/index.css'; @import './accordionpanel.css'; @import './commandpalette.css'; @import './datagrid.css'; @import './dockpanel.css'; @import './menu.css'; @import './menubar.css'; @import './scrollbar.css'; @import './tabbar.css'; lumino-2021.12.13/packages/default-theme/style/index.js000066400000000000000000000011711415564225700225300ustar00rootroot00000000000000/*----------------------------------------------------------------------------- | Copyright (c) 2014-2018, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import '@lumino/dragdrop/style/index'; import '@lumino/widgets/style/index'; import './accordionpanel.css'; import './commandpalette.css'; import './datagrid.css'; import './dockpanel.css'; import './menu.css'; import './menubar.css'; import './scrollbar.css'; import './tabbar.css'; lumino-2021.12.13/packages/default-theme/style/menu.css000066400000000000000000000046241415564225700225470ustar00rootroot00000000000000/*----------------------------------------------------------------------------- | Copyright (c) Jupyter Development Team. | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ /* */ .p-Menu, /* */ .lm-Menu { padding: 3px 0px; background: white; color: rgba(0, 0, 0, 0.87); border: 1px solid #c0c0c0; font: 12px Helvetica, Arial, sans-serif; box-shadow: 0px 1px 6px rgba(0, 0, 0, 0.2); } /* */ .p-Menu-item.p-mod-active, /* */ .lm-Menu-item.lm-mod-active { background: #e5e5e5; } /* */ .p-Menu-item.p-mod-disabled, /* */ .lm-Menu-item.lm-mod-disabled { color: rgba(0, 0, 0, 0.25); } /* */ .p-Menu-itemIcon, /* */ .lm-Menu-itemIcon { width: 21px; padding: 4px 2px; } /* */ .p-Menu-itemLabel, /* */ .lm-Menu-itemLabel { padding: 4px 35px 4px 2px; } /* */ .p-Menu-itemMnemonic, /* */ .lm-Menu-itemMnemonic { text-decoration: underline; } /* */ .p-Menu-itemShortcut, /* */ .lm-Menu-itemShortcut { padding: 4px 0px; } /* */ .p-Menu-itemSubmenuIcon, /* */ .lm-Menu-itemSubmenuIcon { width: 16px; padding: 4px 0px; } /* */ .p-Menu-item[data-type='separator'] > div, /* */ .lm-Menu-item[data-type='separator'] > div { padding: 0; height: 9px; } /* */ .p-Menu-item[data-type='separator'] > div::after, /* */ .lm-Menu-item[data-type='separator'] > div::after { content: ''; display: block; position: relative; top: 4px; border-top: 1px solid #dddddd; } /* */ .p-Menu-itemIcon::before, .p-Menu-itemSubmenuIcon::before, /* */ .lm-Menu-itemIcon::before, .lm-Menu-itemSubmenuIcon::before { font-family: FontAwesome; } /* */ .p-Menu-item.lm-mod-toggled > .p-Menu-itemIcon::before, /* */ .lm-Menu-item.lm-mod-toggled > .lm-Menu-itemIcon::before { content: '\f00c'; } /* */ .p-Menu-item[data-type='submenu'] > .p-Menu-itemSubmenuIcon::before, /* */ .lm-Menu-item[data-type='submenu'] > .lm-Menu-itemSubmenuIcon::before { content: '\f0da'; } lumino-2021.12.13/packages/default-theme/style/menubar.css000066400000000000000000000024651415564225700232350ustar00rootroot00000000000000/*----------------------------------------------------------------------------- | Copyright (c) Jupyter Development Team. | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ /* */ .p-MenuBar, /* */ .lm-MenuBar { padding-left: 5px; background: #fafafa; color: rgba(0, 0, 0, 0.87); border-bottom: 1px solid #dddddd; font: 13px Helvetica, Arial, sans-serif; } /* */ .p-MenuBar-menu, /* */ .lm-MenuBar-menu { transform: translateY(-1px); } /* */ .p-MenuBar-item, /* */ .lm-MenuBar-item { padding: 4px 8px; border-left: 1px solid transparent; border-right: 1px solid transparent; } /* */ .p-MenuBar-item.p-mod-active, /* */ .lm-MenuBar-item.lm-mod-active { background: #e5e5e5; } /* */ .p-MenuBar.p-mod-active .p-MenuBar-item.p-mod-active, /* */ .lm-MenuBar.lm-mod-active .lm-MenuBar-item.lm-mod-active { z-index: 10001; background: white; border-left: 1px solid #c0c0c0; border-right: 1px solid #c0c0c0; box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.2); } lumino-2021.12.13/packages/default-theme/style/scrollbar.css000066400000000000000000000065701415564225700235700ustar00rootroot00000000000000/*----------------------------------------------------------------------------- | Copyright (c) Jupyter Development Team. | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ /* */ .p-ScrollBar[data-orientation='horizontal'], /* */ .lm-ScrollBar[data-orientation='horizontal'] { min-height: 16px; max-height: 16px; min-width: 45px; border-top: 1px solid #a0a0a0; } /* */ .p-ScrollBar[data-orientation='vertical'], /* */ .lm-ScrollBar[data-orientation='vertical'] { min-width: 16px; max-width: 16px; min-height: 45px; border-left: 1px solid #a0a0a0; } /* */ .p-ScrollBar-button, /* */ .lm-ScrollBar-button { background-color: #f0f0f0; background-position: center center; min-height: 15px; max-height: 15px; min-width: 15px; max-width: 15px; } /* */ .p-ScrollBar-button:hover, /* */ .lm-ScrollBar-button:hover { background-color: #dadada; } /* */ .p-ScrollBar-button.p-mod-active, /* */ .lm-ScrollBar-button.lm-mod-active { background-color: #cdcdcd; } /* */ .p-ScrollBar-track, /* */ .lm-ScrollBar-track { background: #f0f0f0; } /* */ .p-ScrollBar-thumb, /* */ .lm-ScrollBar-thumb { background: #cdcdcd; } /* */ .p-ScrollBar-thumb:hover, /* */ .lm-ScrollBar-thumb:hover { background: #bababa; } /* */ .p-ScrollBar-thumb.lm-mod-active, /* */ .lm-ScrollBar-thumb.lm-mod-active { background: #a0a0a0; } /* */ .p-ScrollBar[data-orientation='horizontal'] .p-ScrollBar-thumb, /* */ .lm-ScrollBar[data-orientation='horizontal'] .lm-ScrollBar-thumb { height: 100%; min-width: 15px; border-left: 1px solid #a0a0a0; border-right: 1px solid #a0a0a0; } /* */ .p-ScrollBar[data-orientation='vertical'] .p-ScrollBar-thumb, /* */ .lm-ScrollBar[data-orientation='vertical'] .lm-ScrollBar-thumb { width: 100%; min-height: 15px; border-top: 1px solid #a0a0a0; border-bottom: 1px solid #a0a0a0; } /* */ .p-ScrollBar[data-orientation='horizontal'] .p-ScrollBar-button[data-action='decrement'], /* */ .lm-ScrollBar[data-orientation='horizontal'] .lm-ScrollBar-button[data-action='decrement'] { background-image: url(../images/caretleft.png); } /* */ .p-ScrollBar[data-orientation='horizontal'] .p-ScrollBar-button[data-action='increment'], /* */ .lm-ScrollBar[data-orientation='horizontal'] .lm-ScrollBar-button[data-action='increment'] { background-image: url(../images/caretright.png); } /* */ .p-ScrollBar[data-orientation='vertical'] .p-ScrollBar-button[data-action='decrement'], /* */ .lm-ScrollBar[data-orientation='vertical'] .lm-ScrollBar-button[data-action='decrement'] { background-image: url(../images/caretup.png); } /* */ .p-ScrollBar[data-orientation='vertical'] .p-ScrollBar-button[data-action='increment'], /* */ .lm-ScrollBar[data-orientation='vertical'] .lm-ScrollBar-button[data-action='increment'] { background-image: url(../images/caretdown.png); } lumino-2021.12.13/packages/default-theme/style/tabbar.css000066400000000000000000000052671415564225700230420ustar00rootroot00000000000000/*----------------------------------------------------------------------------- | Copyright (c) Jupyter Development Team. | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ /* */ .p-TabBar, /* */ .lm-TabBar { min-height: 24px; max-height: 24px; } /* */ .p-TabBar-content, /* */ .lm-TabBar-content { min-width: 0; min-height: 0; align-items: flex-end; border-bottom: 1px solid #c0c0c0; } /* */ .p-TabBar-tab, /* */ .lm-TabBar-tab { padding: 0px 10px; background: #e5e5e5; border: 1px solid #c0c0c0; border-bottom: none; font: 12px Helvetica, Arial, sans-serif; flex: 0 1 125px; min-height: 20px; max-height: 20px; min-width: 35px; margin-left: -1px; line-height: 20px; } .lm-TabBar-tabLabel .lm-TabBar-tabInput { padding: 0px; border: 0px; font: 12px Helvetica, Arial, sans-serif; } /* */ .p-TabBar-tab.p-mod-current, /* */ .lm-TabBar-tab.lm-mod-current { background: white; } /* */ .p-TabBar-tab:hover:not(.p-mod-current), /* */ .lm-TabBar-tab:hover:not(.lm-mod-current) { background: #f0f0f0; } /* */ .p-TabBar-tab:first-child, /* */ .lm-TabBar-tab:first-child { margin-left: 0; } /* */ .p-TabBar-tab.p-mod-current, /* */ .lm-TabBar-tab.lm-mod-current { min-height: 23px; max-height: 23px; transform: translateY(1px); } /* */ .p-TabBar-tabIcon, .p-TabBar-tabLabel, .p-TabBar-tabCloseIcon, /* */ .lm-TabBar-tabIcon, .lm-TabBar-tabLabel, .lm-TabBar-tabCloseIcon { display: inline-block; } /* */ .p-TabBar-tab.p-mod-closable > .p-TabBar-tabCloseIcon, /* */ .lm-TabBar-tab.lm-mod-closable > .lm-TabBar-tabCloseIcon { margin-left: 4px; } .lm-TabBar .lm-TabBar-addButton { padding: 0px 6px; border-bottom: 1px solid #c0c0c0; } /* */ .p-TabBar-tab.p-mod-closable > .p-TabBar-tabCloseIcon:before, /* */ .lm-TabBar-tab.lm-mod-closable > .lm-TabBar-tabCloseIcon:before { content: '\f00d'; font-family: FontAwesome; } .lm-TabBar .lm-TabBar-addButton:before { content: '\f067'; font-family: FontAwesome; } /* */ .p-TabBar-tab.p-mod-drag-image, /* */ .lm-TabBar-tab.lm-mod-drag-image { min-height: 23px; max-height: 23px; min-width: 125px; border: none; box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.3); transform: translateX(-40%) translateY(-58%); } lumino-2021.12.13/packages/disposable/000077500000000000000000000000001415564225700173245ustar00rootroot00000000000000lumino-2021.12.13/packages/disposable/api-extractor.json000066400000000000000000000014611415564225700230030ustar00rootroot00000000000000/** * Config file for API Extractor. For more info, please visit: https://api-extractor.com */ { "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", /** * Optionally specifies another JSON config file that this file extends from. This provides a way for * standard settings to be shared across multiple projects. * * If the path starts with "./" or "../", the path is resolved relative to the folder of the file that contains * the "extends" field. Otherwise, the first path segment is interpreted as an NPM package name, and will be * resolved using NodeJS require(). * * SUPPORTED TOKENS: none * DEFAULT VALUE: "" */ "extends": "../../api-extractor-base.json" // "extends": "my-package/include/api-extractor-base.json" } lumino-2021.12.13/packages/disposable/package.json000066400000000000000000000051041415564225700216120ustar00rootroot00000000000000{ "name": "@lumino/disposable", "version": "1.10.1", "description": "Lumino Disposable", "homepage": "https://github.com/jupyterlab/lumino", "bugs": { "url": "https://github.com/jupyterlab/lumino/issues" }, "repository": { "type": "git", "url": "https://github.com/jupyterlab/lumino.git" }, "license": "BSD-3-Clause", "author": "S. Chris Colbert ", "contributors": [ "A. Darian ", "Dave Willmer ", "S. Chris Colbert ", "Steven Silvester " ], "main": "dist/index.js", "jsdelivr": "dist/index.min.js", "unpkg": "dist/index.min.js", "module": "dist/index.es6", "types": "types/index.d.ts", "files": [ "dist/*", "src/*", "types/*" ], "scripts": { "api": "api-extractor run --local --verbose", "build": "npm run build:src && rollup -c", "build:src": "tsc --build", "build:test": "tsc --build tests && cd tests && webpack", "clean": "rimraf ./lib && rimraf *.tsbuildinfo && rimraf ./types && rimraf ./dist", "clean:test": "rimraf tests/build", "docs": "typedoc --options tdoptions.json src", "minimize": "terser dist/index.js -c -m --source-map \"content='dist/index.js.map',url='index.min.js.map'\" -o dist/index.min.js", "test": "npm run test:firefox-headless", "test:chrome": "cd tests && karma start --browsers=Chrome", "test:chrome-headless": "cd tests && karma start --browsers=ChromeHeadless", "test:firefox": "cd tests && karma start --browsers=Firefox", "test:firefox-headless": "cd tests && karma start --browsers=FirefoxHeadless", "test:ie": "cd tests && karma start --browsers=IE", "watch": "tsc --build --watch" }, "dependencies": { "@lumino/algorithm": "^1.9.1", "@lumino/signaling": "^1.10.1" }, "devDependencies": { "@microsoft/api-extractor": "^7.6.0", "@types/chai": "^3.4.35", "@types/mocha": "^2.2.39", "chai": "^4.3.4", "karma": "^6.3.4", "karma-chrome-launcher": "^3.1.0", "karma-firefox-launcher": "^2.1.1", "karma-ie-launcher": "^1.0.0", "karma-mocha": "^2.0.1", "karma-mocha-reporter": "^2.2.5", "mocha": "^9.0.3", "rimraf": "^3.0.2", "rollup": "^2.56.0", "rollup-plugin-node-resolve": "^5.2.0", "rollup-plugin-postcss": "^4.0.0", "rollup-plugin-sourcemaps": "^0.6.3", "terser": "^5.7.1", "tslib": "^2.3.0", "typedoc": "~0.15.0", "typescript": "~3.6.0", "webpack": "^4.41.3", "webpack-cli": "^3.3.10" }, "publishConfig": { "access": "public" } } lumino-2021.12.13/packages/disposable/rollup.config.js000066400000000000000000000015231415564225700224440ustar00rootroot00000000000000import nodeResolve from 'rollup-plugin-node-resolve'; import sourcemaps from 'rollup-plugin-sourcemaps'; import postcss from 'rollup-plugin-postcss'; const pkg = require('./package.json'); const globals = id => id.indexOf('@lumino/') === 0 ? id.replace('@lumino/', 'lumino_') : id; export default [ { input: 'lib/index', external: id => pkg.dependencies && !!pkg.dependencies[id], output: [ { file: pkg.main, globals, format: 'umd', sourcemap: true, name: pkg.name }, { file: pkg.module + '.js', format: 'es', sourcemap: true, name: pkg.name } ], plugins: [ nodeResolve({ preferBuiltins: true }), sourcemaps(), postcss({ extensions: ['.css'], minimize: true }) ] } ]; lumino-2021.12.13/packages/disposable/src/000077500000000000000000000000001415564225700201135ustar00rootroot00000000000000lumino-2021.12.13/packages/disposable/src/index.ts000066400000000000000000000135561415564225700216040ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { each, IterableOrArrayLike } from '@lumino/algorithm'; import { ISignal, Signal } from '@lumino/signaling'; /** * An object which implements the disposable pattern. */ export interface IDisposable { /** * Test whether the object has been disposed. * * #### Notes * This property is always safe to access. */ readonly isDisposed: boolean; /** * Dispose of the resources held by the object. * * #### Notes * If the object's `dispose` method is called more than once, all * calls made after the first will be a no-op. * * #### Undefined Behavior * It is undefined behavior to use any functionality of the object * after it has been disposed unless otherwise explicitly noted. */ dispose(): void; } /** * A disposable object with an observable `disposed` signal. */ export interface IObservableDisposable extends IDisposable { /** * A signal emitted when the object is disposed. */ readonly disposed: ISignal; } /** * A disposable object which delegates to a callback function. */ export class DisposableDelegate implements IDisposable { /** * Construct a new disposable delegate. * * @param fn - The callback function to invoke on dispose. */ constructor(fn: () => void) { this._fn = fn; } /** * Test whether the delegate has been disposed. */ get isDisposed(): boolean { return !this._fn; } /** * Dispose of the delegate and invoke the callback function. */ dispose(): void { if (!this._fn) { return; } let fn = this._fn; this._fn = null; fn(); } private _fn: (() => void) | null; } /** * An observable disposable object which delegates to a callback function. */ export class ObservableDisposableDelegate extends DisposableDelegate implements IObservableDisposable { /** * A signal emitted when the delegate is disposed. */ get disposed(): ISignal { return this._disposed; } /** * Dispose of the delegate and invoke the callback function. */ dispose(): void { if (this.isDisposed) { return; } super.dispose(); this._disposed.emit(undefined); Signal.clearData(this); } private _disposed = new Signal(this); } /** * An object which manages a collection of disposable items. */ export class DisposableSet implements IDisposable { /** * Test whether the set has been disposed. */ get isDisposed(): boolean { return this._isDisposed; } /** * Dispose of the set and the items it contains. * * #### Notes * Items are disposed in the order they are added to the set. */ dispose(): void { if (this._isDisposed) { return; } this._isDisposed = true; this._items.forEach(item => { item.dispose(); }); this._items.clear(); } /** * Test whether the set contains a specific item. * * @param item - The item of interest. * * @returns `true` if the set contains the item, `false` otherwise. */ contains(item: IDisposable): boolean { return this._items.has(item); } /** * Add a disposable item to the set. * * @param item - The item to add to the set. * * #### Notes * If the item is already contained in the set, this is a no-op. */ add(item: IDisposable): void { this._items.add(item); } /** * Remove a disposable item from the set. * * @param item - The item to remove from the set. * * #### Notes * If the item is not contained in the set, this is a no-op. */ remove(item: IDisposable): void { this._items.delete(item); } /** * Remove all items from the set. */ clear(): void { this._items.clear(); } private _isDisposed = false; private _items = new Set(); } /** * The namespace for the `DisposableSet` class statics. */ export namespace DisposableSet { /** * Create a disposable set from an iterable of items. * * @param items - The iterable or array-like object of interest. * * @returns A new disposable initialized with the given items. */ export function from(items: IterableOrArrayLike): DisposableSet { let set = new DisposableSet(); each(items, item => { set.add(item); }); return set; } } /** * An observable object which manages a collection of disposable items. */ export class ObservableDisposableSet extends DisposableSet implements IObservableDisposable { /** * A signal emitted when the set is disposed. */ get disposed(): ISignal { return this._disposed; } /** * Dispose of the set and the items it contains. * * #### Notes * Items are disposed in the order they are added to the set. */ dispose(): void { if (this.isDisposed) { return; } super.dispose(); this._disposed.emit(undefined); Signal.clearData(this); } private _disposed = new Signal(this); } /** * The namespace for the `ObservableDisposableSet` class statics. */ export namespace ObservableDisposableSet { /** * Create an observable disposable set from an iterable of items. * * @param items - The iterable or array-like object of interest. * * @returns A new disposable initialized with the given items. */ export function from( items: IterableOrArrayLike ): ObservableDisposableSet { let set = new ObservableDisposableSet(); each(items, item => { set.add(item); }); return set; } } lumino-2021.12.13/packages/disposable/tdoptions.json000066400000000000000000000005211415564225700222400ustar00rootroot00000000000000{ "excludeNotExported": true, "mode": "file", "target": "es5", "module": "es5", "lib": [ "lib.es2015.d.ts", "lib.es2015.collection.d.ts", "lib.es2015.promise.d.ts", "lib.dom.d.ts" ], "out": "../../docs/source/api/disposable", "baseUrl": ".", "paths": { "@lumino/*": ["node_modules/@lumino/*"] } } lumino-2021.12.13/packages/disposable/tests/000077500000000000000000000000001415564225700204665ustar00rootroot00000000000000lumino-2021.12.13/packages/disposable/tests/karma.conf.js000066400000000000000000000005051415564225700230430ustar00rootroot00000000000000module.exports = function (config) { config.set({ basePath: '.', frameworks: ['mocha'], reporters: ['mocha'], files: ['build/bundle.test.js'], port: 9876, colors: true, singleRun: true, browserNoActivityTimeout: 30000, failOnEmptyTestSuite: false, logLevel: config.LOG_INFO }); }; lumino-2021.12.13/packages/disposable/tests/src/000077500000000000000000000000001415564225700212555ustar00rootroot00000000000000lumino-2021.12.13/packages/disposable/tests/src/index.spec.ts000066400000000000000000000242261415564225700236730ustar00rootroot00000000000000/* eslint-disable @typescript-eslint/no-empty-function */ // Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2016, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { expect } from 'chai'; import { DisposableDelegate, DisposableSet, IDisposable, ObservableDisposableDelegate, ObservableDisposableSet } from '@lumino/disposable'; class TestDisposable implements IDisposable { count = 0; get isDisposed(): boolean { return this.count > 0; } dispose(): void { this.count++; } } describe('@lumino/disposable', () => { describe('DisposableDelegate', () => { describe('#constructor()', () => { it('should accept a callback', () => { let delegate = new DisposableDelegate(() => {}); expect(delegate).to.be.an.instanceof(DisposableDelegate); }); }); describe('#isDisposed', () => { it('should be `false` before object is disposed', () => { let delegate = new DisposableDelegate(() => {}); expect(delegate.isDisposed).to.equal(false); }); it('should be `true` after object is disposed', () => { let delegate = new DisposableDelegate(() => {}); delegate.dispose(); expect(delegate.isDisposed).to.equal(true); }); }); describe('#dispose()', () => { it('should invoke a callback when disposed', () => { let called = false; let delegate = new DisposableDelegate(() => (called = true)); expect(called).to.equal(false); delegate.dispose(); expect(called).to.equal(true); }); it('should ignore multiple calls to `dispose`', () => { let count = 0; let delegate = new DisposableDelegate(() => count++); expect(count).to.equal(0); delegate.dispose(); delegate.dispose(); delegate.dispose(); expect(count).to.equal(1); }); }); }); describe('ObservableDisposableDelegate', () => { describe('#disposed', () => { it('should be emitted when the delegate is disposed', () => { let called = false; let delegate = new ObservableDisposableDelegate(() => {}); delegate.disposed.connect(() => { called = true; }); delegate.dispose(); expect(called).to.equal(true); }); }); }); describe('DisposableSet', () => { describe('#constructor()', () => { it('should accept no arguments', () => { let set = new DisposableSet(); expect(set).to.be.an.instanceof(DisposableSet); }); }); describe('#isDisposed', () => { it('should be `false` before object is disposed', () => { let set = new DisposableSet(); expect(set.isDisposed).to.equal(false); }); it('should be `true` after object is disposed', () => { let set = new DisposableSet(); set.dispose(); expect(set.isDisposed).to.equal(true); }); }); describe('#dispose()', () => { it('should dispose all items in the set', () => { let item1 = new TestDisposable(); let item2 = new TestDisposable(); let item3 = new TestDisposable(); let set = DisposableSet.from([item1, item2, item3]); expect(item1.count).to.equal(0); expect(item2.count).to.equal(0); expect(item3.count).to.equal(0); set.dispose(); expect(item1.count).to.equal(1); expect(item2.count).to.equal(1); expect(item3.count).to.equal(1); }); it('should dipose items in the order they were added', () => { let values: number[] = []; let item1 = new DisposableDelegate(() => values.push(0)); let item2 = new DisposableDelegate(() => values.push(1)); let item3 = new DisposableDelegate(() => values.push(2)); let set = DisposableSet.from([item1, item2, item3]); expect(values).to.deep.equal([]); set.dispose(); expect(values).to.deep.equal([0, 1, 2]); }); it('should ignore multiple calls to `dispose`', () => { let item1 = new TestDisposable(); let item2 = new TestDisposable(); let item3 = new TestDisposable(); let set = DisposableSet.from([item1, item2, item3]); expect(item1.count).to.equal(0); expect(item2.count).to.equal(0); expect(item3.count).to.equal(0); set.dispose(); set.dispose(); set.dispose(); expect(item1.count).to.equal(1); expect(item2.count).to.equal(1); expect(item3.count).to.equal(1); }); }); describe('#add()', () => { it('should add items to the set', () => { let item1 = new TestDisposable(); let item2 = new TestDisposable(); let item3 = new TestDisposable(); let set = new DisposableSet(); set.add(item1); set.add(item2); set.add(item3); expect(item1.count).to.equal(0); expect(item2.count).to.equal(0); expect(item3.count).to.equal(0); set.dispose(); expect(item1.count).to.equal(1); expect(item2.count).to.equal(1); expect(item3.count).to.equal(1); }); it('should maintain insertion order', () => { let values: number[] = []; let item1 = new DisposableDelegate(() => values.push(0)); let item2 = new DisposableDelegate(() => values.push(1)); let item3 = new DisposableDelegate(() => values.push(2)); let set = DisposableSet.from([item1]); set.add(item2); set.add(item3); expect(values).to.deep.equal([]); set.dispose(); expect(values).to.deep.equal([0, 1, 2]); }); it('should ignore duplicate items', () => { let values: number[] = []; let item1 = new DisposableDelegate(() => values.push(0)); let item2 = new DisposableDelegate(() => values.push(1)); let item3 = new DisposableDelegate(() => values.push(2)); let set = DisposableSet.from([item1]); set.add(item2); set.add(item3); set.add(item3); set.add(item2); set.add(item1); expect(values).to.deep.equal([]); set.dispose(); expect(values).to.deep.equal([0, 1, 2]); }); }); describe('#remove()', () => { it('should remove items from the set', () => { let item1 = new TestDisposable(); let item2 = new TestDisposable(); let item3 = new TestDisposable(); let set = DisposableSet.from([item1, item2, item3]); expect(item1.count).to.equal(0); expect(item2.count).to.equal(0); expect(item3.count).to.equal(0); set.remove(item2); set.dispose(); expect(item1.count).to.equal(1); expect(item2.count).to.equal(0); expect(item3.count).to.equal(1); }); it('should maintain insertion order', () => { let values: number[] = []; let item1 = new DisposableDelegate(() => values.push(0)); let item2 = new DisposableDelegate(() => values.push(1)); let item3 = new DisposableDelegate(() => values.push(2)); let set = DisposableSet.from([item1, item2, item3]); expect(values).to.deep.equal([]); set.remove(item1); set.dispose(); expect(values).to.deep.equal([1, 2]); }); it('should ignore missing items', () => { let values: number[] = []; let item1 = new DisposableDelegate(() => values.push(0)); let item2 = new DisposableDelegate(() => values.push(1)); let item3 = new DisposableDelegate(() => values.push(2)); let set = DisposableSet.from([item1, item2]); expect(values).to.deep.equal([]); set.remove(item3); set.dispose(); expect(values).to.deep.equal([0, 1]); }); }); describe('#clear()', () => { it('remove all items from the set', () => { let item1 = new TestDisposable(); let item2 = new TestDisposable(); let item3 = new TestDisposable(); let set = DisposableSet.from([item1, item2, item3]); expect(item1.count).to.equal(0); expect(item2.count).to.equal(0); expect(item3.count).to.equal(0); set.clear(); set.dispose(); expect(item1.count).to.equal(0); expect(item2.count).to.equal(0); expect(item3.count).to.equal(0); }); }); describe('.from()', () => { it('should accept an iterable of disposable items', () => { let item1 = new TestDisposable(); let item2 = new TestDisposable(); let item3 = new TestDisposable(); let set = DisposableSet.from([item1, item2, item3]); expect(set).to.be.an.instanceof(DisposableSet); }); }); }); describe('ObservableDisposableSet', () => { describe('#disposed', () => { it('should be emitted when the set is disposed', () => { let called = false; let set = new ObservableDisposableSet(); let item1 = new TestDisposable(); let item2 = new TestDisposable(); let item3 = new TestDisposable(); set.add(item1); set.add(item2); set.add(item3); set.disposed.connect(() => { expect(set.contains(item1)).to.equal(false); expect(set.contains(item2)).to.equal(false); expect(set.contains(item3)).to.equal(false); expect(item1.isDisposed).to.equal(true); expect(item2.isDisposed).to.equal(true); expect(item3.isDisposed).to.equal(true); called = true; }); set.dispose(); expect(called).to.equal(true); }); }); describe('.from()', () => { it('should accept an iterable of disposable items', () => { let item1 = new TestDisposable(); let item2 = new TestDisposable(); let item3 = new TestDisposable(); let set = ObservableDisposableSet.from([item1, item2, item3]); expect(set).to.be.an.instanceof(ObservableDisposableSet); }); }); }); }); lumino-2021.12.13/packages/disposable/tests/tsconfig.json000066400000000000000000000007661415564225700232060ustar00rootroot00000000000000{ "compilerOptions": { "composite": true, "declaration": true, "noImplicitAny": true, "noEmitOnError": true, "noUnusedLocals": true, "strictNullChecks": true, "module": "commonjs", "moduleResolution": "node", "target": "ES5", "outDir": "build", "lib": ["ES5"], "types": ["chai", "mocha"], "rootDir": "src" }, "include": ["src/*"], "references": [ { "path": "../../algorithm" }, { "path": "../../signaling" } ] } lumino-2021.12.13/packages/disposable/tests/webpack.config.js000066400000000000000000000003061415564225700237030ustar00rootroot00000000000000var path = require('path'); module.exports = { entry: './build/index.spec.js', mode: 'development', output: { filename: './build/bundle.test.js', path: path.resolve(__dirname) } }; lumino-2021.12.13/packages/disposable/tsconfig.json000066400000000000000000000011611415564225700220320ustar00rootroot00000000000000{ "compilerOptions": { "composite": true, "sourceMap": true, "declaration": true, "declarationDir": "./types", "declarationMap": true, "noImplicitAny": true, "noEmitOnError": true, "noUnusedLocals": true, "strictNullChecks": true, "module": "ES6", "moduleResolution": "node", "target": "ES5", "rootDir": "src", "outDir": "lib", "lib": ["ES5", "ES2015.Collection", "ES2015.Iterable"], "importHelpers": true, "types": [] }, "include": ["src/*"], "references": [ { "path": "../algorithm" }, { "path": "../signaling" } ] } lumino-2021.12.13/packages/domutils/000077500000000000000000000000001415564225700170375ustar00rootroot00000000000000lumino-2021.12.13/packages/domutils/api-extractor.json000066400000000000000000000014611415564225700225160ustar00rootroot00000000000000/** * Config file for API Extractor. For more info, please visit: https://api-extractor.com */ { "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", /** * Optionally specifies another JSON config file that this file extends from. This provides a way for * standard settings to be shared across multiple projects. * * If the path starts with "./" or "../", the path is resolved relative to the folder of the file that contains * the "extends" field. Otherwise, the first path segment is interpreted as an NPM package name, and will be * resolved using NodeJS require(). * * SUPPORTED TOKENS: none * DEFAULT VALUE: "" */ "extends": "../../api-extractor-base.json" // "extends": "my-package/include/api-extractor-base.json" } lumino-2021.12.13/packages/domutils/package.json000066400000000000000000000047451415564225700213370ustar00rootroot00000000000000{ "name": "@lumino/domutils", "version": "1.8.1", "description": "Lumino DOM Utilities", "homepage": "https://github.com/jupyterlab/lumino", "bugs": { "url": "https://github.com/jupyterlab/lumino/issues" }, "repository": { "type": "git", "url": "https://github.com/jupyterlab/lumino.git" }, "license": "BSD-3-Clause", "author": "S. Chris Colbert ", "contributors": [ "A. Darian ", "Dave Willmer ", "S. Chris Colbert ", "Steven Silvester " ], "main": "dist/index.js", "jsdelivr": "dist/index.min.js", "unpkg": "dist/index.min.js", "module": "dist/index.es6", "types": "types/index.d.ts", "files": [ "dist/*", "src/*", "types/*" ], "scripts": { "api": "api-extractor run --local --verbose", "build": "npm run build:src && rollup -c", "build:src": "tsc --build", "build:test": "tsc --build tests && cd tests && webpack", "clean": "rimraf ./lib && rimraf *.tsbuildinfo && rimraf ./types && rimraf ./dist", "clean:test": "rimraf tests/build", "docs": "typedoc --options tdoptions.json src", "minimize": "terser dist/index.js -c -m --source-map \"content='dist/index.js.map',url='index.min.js.map'\" -o dist/index.min.js", "test": "npm run test:firefox-headless", "test:chrome": "cd tests && karma start --browsers=Chrome", "test:chrome-headless": "cd tests && karma start --browsers=ChromeHeadless", "test:firefox": "cd tests && karma start --browsers=Firefox", "test:firefox-headless": "cd tests && karma start --browsers=FirefoxHeadless", "test:ie": "cd tests && karma start --browsers=IE", "watch": "tsc --build --watch" }, "devDependencies": { "@microsoft/api-extractor": "^7.6.0", "@types/chai": "^3.4.35", "@types/mocha": "^2.2.39", "chai": "^4.3.4", "karma": "^6.3.4", "karma-chrome-launcher": "^3.1.0", "karma-firefox-launcher": "^2.1.1", "karma-ie-launcher": "^1.0.0", "karma-mocha": "^2.0.1", "karma-mocha-reporter": "^2.2.5", "mocha": "^9.0.3", "rimraf": "^3.0.2", "rollup": "^2.56.0", "rollup-plugin-node-resolve": "^5.2.0", "rollup-plugin-postcss": "^4.0.0", "rollup-plugin-sourcemaps": "^0.6.3", "terser": "^5.7.1", "tslib": "^2.3.0", "typedoc": "~0.15.0", "typescript": "~3.6.0", "webpack": "^4.41.3", "webpack-cli": "^3.3.10" }, "publishConfig": { "access": "public" } } lumino-2021.12.13/packages/domutils/rollup.config.js000066400000000000000000000015231415564225700221570ustar00rootroot00000000000000import nodeResolve from 'rollup-plugin-node-resolve'; import sourcemaps from 'rollup-plugin-sourcemaps'; import postcss from 'rollup-plugin-postcss'; const pkg = require('./package.json'); const globals = id => id.indexOf('@lumino/') === 0 ? id.replace('@lumino/', 'lumino_') : id; export default [ { input: 'lib/index', external: id => pkg.dependencies && !!pkg.dependencies[id], output: [ { file: pkg.main, globals, format: 'umd', sourcemap: true, name: pkg.name }, { file: pkg.module + '.js', format: 'es', sourcemap: true, name: pkg.name } ], plugins: [ nodeResolve({ preferBuiltins: true }), sourcemaps(), postcss({ extensions: ['.css'], minimize: true }) ] } ]; lumino-2021.12.13/packages/domutils/src/000077500000000000000000000000001415564225700176265ustar00rootroot00000000000000lumino-2021.12.13/packages/domutils/src/clipboard.ts000066400000000000000000000024171415564225700221410ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2019, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ /** * The namespace for clipboard related functionality. */ export namespace ClipboardExt { /** * Copy text to the system clipboard. * * @param text - The text to copy to the clipboard. */ export function copyText(text: string): void { // Fetch the document body. const body = document.body; // Set up the clipboard event listener. const handler = (event: ClipboardEvent) => { // Stop the event propagation. event.preventDefault(); event.stopPropagation(); // Set the clipboard data. event.clipboardData!.setData('text', text); // Remove the event listener. body.removeEventListener('copy', handler, true); }; // Add the event listener. body.addEventListener('copy', handler, true); // Trigger the event. document.execCommand('copy'); } } lumino-2021.12.13/packages/domutils/src/element.ts000066400000000000000000000127161415564225700216360ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ /** * The namespace for element related utilities. */ export namespace ElementExt { /** * An object which holds the border and padding data for an element. */ export interface IBoxSizing { /** * The top border width, in pixels. */ borderTop: number; /** * The left border width, in pixels. */ borderLeft: number; /** * The right border width, in pixels. */ borderRight: number; /** * The bottom border width, in pixels. */ borderBottom: number; /** * The top padding width, in pixels. */ paddingTop: number; /** * The left padding width, in pixels. */ paddingLeft: number; /** * The right padding width, in pixels. */ paddingRight: number; /** * The bottom padding width, in pixels. */ paddingBottom: number; /** * The sum of horizontal border and padding. */ horizontalSum: number; /** * The sum of vertical border and padding. */ verticalSum: number; } /** * Compute the box sizing for an element. * * @param element - The element of interest. * * @returns The box sizing data for the specified element. */ export function boxSizing(element: Element): IBoxSizing { let style = window.getComputedStyle(element); let bt = parseFloat(style.borderTopWidth!) || 0; let bl = parseFloat(style.borderLeftWidth!) || 0; let br = parseFloat(style.borderRightWidth!) || 0; let bb = parseFloat(style.borderBottomWidth!) || 0; let pt = parseFloat(style.paddingTop!) || 0; let pl = parseFloat(style.paddingLeft!) || 0; let pr = parseFloat(style.paddingRight!) || 0; let pb = parseFloat(style.paddingBottom!) || 0; let hs = bl + pl + pr + br; let vs = bt + pt + pb + bb; return { borderTop: bt, borderLeft: bl, borderRight: br, borderBottom: bb, paddingTop: pt, paddingLeft: pl, paddingRight: pr, paddingBottom: pb, horizontalSum: hs, verticalSum: vs }; } /** * An object which holds the min and max size data for an element. */ export interface ISizeLimits { /** * The minimum width, in pixels. */ minWidth: number; /** * The minimum height, in pixels. */ minHeight: number; /** * The maximum width, in pixels. */ maxWidth: number; /** * The maximum height, in pixels. */ maxHeight: number; } /** * Compute the size limits for an element. * * @param element - The element of interest. * * @returns The size limit data for the specified element. */ export function sizeLimits(element: Element): ISizeLimits { let style = window.getComputedStyle(element); let minWidth = parseFloat(style.minWidth!) || 0; let minHeight = parseFloat(style.minHeight!) || 0; let maxWidth = parseFloat(style.maxWidth!) || Infinity; let maxHeight = parseFloat(style.maxHeight!) || Infinity; maxWidth = Math.max(minWidth, maxWidth); maxHeight = Math.max(minHeight, maxHeight); return { minWidth, minHeight, maxWidth, maxHeight }; } /** * Test whether a client position lies within an element. * * @param element - The DOM element of interest. * * @param clientX - The client X coordinate of interest. * * @param clientY - The client Y coordinate of interest. * * @returns Whether the point is within the given element. */ export function hitTest( element: Element, clientX: number, clientY: number ): boolean { let rect = element.getBoundingClientRect(); return ( clientX >= rect.left && clientX < rect.right && clientY >= rect.top && clientY < rect.bottom ); } /** * Vertically scroll an element into view if needed. * * @param area - The scroll area element. * * @param element - The element of interest. * * #### Notes * This follows the "nearest" behavior of the native `scrollIntoView` * method, which is not supported by all browsers. * https://drafts.csswg.org/cssom-view/#element-scrolling-members * * If the element fully covers the visible area or is fully contained * within the visible area, no scrolling will take place. Otherwise, * the nearest edges of the area and element are aligned. */ export function scrollIntoViewIfNeeded( area: Element, element: Element ): void { let ar = area.getBoundingClientRect(); let er = element.getBoundingClientRect(); if (er.top <= ar.top && er.bottom >= ar.bottom) { return; } if (er.top < ar.top && er.height <= ar.height) { area.scrollTop -= ar.top - er.top; return; } if (er.bottom > ar.bottom && er.height >= ar.height) { area.scrollTop -= ar.top - er.top; return; } if (er.top < ar.top && er.height > ar.height) { area.scrollTop -= ar.bottom - er.bottom; return; } if (er.bottom > ar.bottom && er.height < ar.height) { area.scrollTop -= ar.bottom - er.bottom; return; } } } lumino-2021.12.13/packages/domutils/src/index.ts000066400000000000000000000010641415564225700213060ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ export * from './clipboard'; export * from './element'; export * from './platform'; export * from './selector'; lumino-2021.12.13/packages/domutils/src/platform.ts000066400000000000000000000027111415564225700220230ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ /** * The namespace for platform related utilities. */ export namespace Platform { /** * A flag indicating whether the platform is Mac. */ export const IS_MAC = !!navigator.platform.match(/Mac/i); /** * A flag indicating whether the platform is Windows. */ export const IS_WIN = !!navigator.platform.match(/Win/i); /** * A flag indicating whether the browser is IE. */ export const IS_IE = /Trident/.test(navigator.userAgent); /** * A flag indicating whether the browser is Edge. */ export const IS_EDGE = /Edge/.test(navigator.userAgent); /** * Test whether the `accel` key is pressed. * * @param event - The keyboard or mouse event of interest. * * @returns Whether the `accel` key is pressed. * * #### Notes * On Mac the `accel` key is the command key. On all other * platforms the `accel` key is the control key. */ export function accelKey(event: KeyboardEvent | MouseEvent): boolean { return IS_MAC ? event.metaKey : event.ctrlKey; } } lumino-2021.12.13/packages/domutils/src/selector.ts000066400000000000000000000157661415564225700220350ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ /** * The namespace for selector related utilities. */ export namespace Selector { /** * Calculate the specificity of a single CSS selector. * * @param selector - The CSS selector of interest. * * @returns The specificity of the selector. * * #### Undefined Behavior * The selector is invalid. * * #### Notes * This is based on https://www.w3.org/TR/css3-selectors/#specificity * * A larger number represents a more specific selector. * * The smallest possible specificity is `0`. * * The result is represented as a hex number `0x` where * each component is the count of the respective selector clause. * * If the selector contains commas, only the first clause is used. * * The computed result is cached, so subsequent calculations for the * same selector are extremely fast. */ export function calculateSpecificity(selector: string): number { if (selector in Private.specificityCache) { return Private.specificityCache[selector]; } let result = Private.calculateSingle(selector); return (Private.specificityCache[selector] = result); } /** * Test whether a selector is a valid CSS selector. * * @param selector - The CSS selector of interest. * * @returns `true` if the selector is valid, `false` otherwise. * * #### Notes * The computed result is cached, so subsequent tests for the same * selector are extremely fast. */ export function isValid(selector: string): boolean { if (selector in Private.validityCache) { return Private.validityCache[selector]; } let result = true; try { Private.testElem.querySelector(selector); } catch (err) { result = false; } return (Private.validityCache[selector] = result); } /** * Test whether an element matches a CSS selector. * * @param element - The element of interest. * * @param selector - The valid CSS selector of interest. * * @returns `true` if the element is a match, `false` otherwise. * * #### Notes * This function uses the builtin browser capabilities when possible, * falling back onto a document query otherwise. */ export function matches(element: Element, selector: string): boolean { return Private.protoMatchFunc.call(element, selector); } } /** * The namespace for the module implementation details. */ namespace Private { /** * A type alias for an object hash. */ export type StringMap = { [key: string]: T }; /** * A cache of computed selector specificity values. */ export const specificityCache: StringMap = Object.create(null); /** * A cache of computed selector validity. */ export const validityCache: StringMap = Object.create(null); /** * An empty element for testing selector validity. */ export const testElem = document.createElement('div'); /** * A cross-browser CSS selector matching prototype function. */ export const protoMatchFunc: Function = (() => { let proto = Element.prototype as any; return ( proto.matches || proto.matchesSelector || proto.mozMatchesSelector || proto.msMatchesSelector || proto.oMatchesSelector || proto.webkitMatchesSelector || function (selector: string) { let elem = this as Element; let matches = elem.ownerDocument ? elem.ownerDocument.querySelectorAll(selector) : []; return Array.prototype.indexOf.call(matches, elem) !== -1; } ); })(); /** * Calculate the specificity of a single selector. * * The behavior is undefined if the selector is invalid. */ export function calculateSingle(selector: string): number { // Ignore anything after the first comma. selector = selector.split(',', 1)[0]; // Setup the aggregate counters. let a = 0; let b = 0; let c = 0; // Apply a regex to the front of the selector. If it succeeds, that // portion of the selector is removed. Returns a success/fail flag. function match(re: RegExp): boolean { let match = selector.match(re); if (match === null) { return false; } selector = selector.slice(match[0].length); return true; } // Replace the negation pseudo-class (which is ignored), // but keep its inner content (which is not ignored). selector = selector.replace(NEGATION_RE, ' $1 '); // Continue matching until the selector is consumed. while (selector.length > 0) { // Match an ID selector. if (match(ID_RE)) { a++; continue; } // Match a class selector. if (match(CLASS_RE)) { b++; continue; } // Match an attribute selector. if (match(ATTR_RE)) { b++; continue; } // Match a pseudo-element selector. This is done before matching // a pseudo-class since this regex overlaps with that regex. if (match(PSEUDO_ELEM_RE)) { c++; continue; } // Match a pseudo-class selector. if (match(PSEDUO_CLASS_RE)) { b++; continue; } // Match a plain type selector. if (match(TYPE_RE)) { c++; continue; } // Finally, match any ignored characters. if (match(IGNORE_RE)) { continue; } // At this point, the selector is assumed to be invalid. return 0; } // Clamp each component to a reasonable base. a = Math.min(a, 0xff); b = Math.min(b, 0xff); c = Math.min(c, 0xff); // Combine the components into a single result. return (a << 16) | (b << 8) | c; } /** * A regex which matches an ID selector at string start. */ const ID_RE = /^#[^\s\+>~#\.\[:]+/; /** * A regex which matches a class selector at string start. */ const CLASS_RE = /^\.[^\s\+>~#\.\[:]+/; /** * A regex which matches an attribute selector at string start. */ const ATTR_RE = /^\[[^\]]+\]/; /** * A regex which matches a type selector at string start. */ const TYPE_RE = /^[^\s\+>~#\.\[:]+/; /** * A regex which matches a pseudo-element selector at string start. */ const PSEUDO_ELEM_RE = /^(::[^\s\+>~#\.\[:]+|:first-line|:first-letter|:before|:after)/; /** * A regex which matches a pseudo-class selector at string start. */ const PSEDUO_CLASS_RE = /^:[^\s\+>~#\.\[:]+/; /** * A regex which matches ignored characters at string start. */ const IGNORE_RE = /^[\s\+>~\*]+/; /** * A regex which matches the negation pseudo-class globally. */ const NEGATION_RE = /:not\(([^\)]+)\)/g; } lumino-2021.12.13/packages/domutils/tdoptions.json000066400000000000000000000005171415564225700217600ustar00rootroot00000000000000{ "excludeNotExported": true, "mode": "file", "target": "es5", "module": "es5", "lib": [ "lib.es2015.d.ts", "lib.es2015.collection.d.ts", "lib.es2015.promise.d.ts", "lib.dom.d.ts" ], "out": "../../docs/source/api/domutils", "baseUrl": ".", "paths": { "@lumino/*": ["node_modules/@lumino/*"] } } lumino-2021.12.13/packages/domutils/tests/000077500000000000000000000000001415564225700202015ustar00rootroot00000000000000lumino-2021.12.13/packages/domutils/tests/karma.conf.js000066400000000000000000000005051415564225700225560ustar00rootroot00000000000000module.exports = function (config) { config.set({ basePath: '.', frameworks: ['mocha'], reporters: ['mocha'], files: ['build/bundle.test.js'], port: 9876, colors: true, singleRun: true, browserNoActivityTimeout: 30000, failOnEmptyTestSuite: false, logLevel: config.LOG_INFO }); }; lumino-2021.12.13/packages/domutils/tests/src/000077500000000000000000000000001415564225700207705ustar00rootroot00000000000000lumino-2021.12.13/packages/domutils/tests/src/element.spec.ts000066400000000000000000000161021415564225700237220ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { expect } from 'chai'; import { ElementExt } from '@lumino/domutils'; const STYLE_TEXT = ` .box-sizing { border-top: solid 10px black; border-left: solid 15px black; padding: 15px 8px 9px 12px; } .size-limits { min-width: 90px; min-height: 95px; max-width: 100px; max-height: 105px; } .hit-test { position: absolute; top: 0; left: 0; width: 100px; height: 100px; } .scroll-area { position: absolute; overflow: auto; top: 0px; left: 0px; width: 300px; height: 600px; } .scroll-elemA { position: absolute; top: 50px; left: 50px; width: 100px; height: 700px; } .scroll-elemB { position: absolute; top: 70px; left: 100px; width: 100px; height: 100px; } `; describe('@lumino/domutils', () => { describe('ElementExt', () => { const styleNode = document.createElement('style'); before(() => { styleNode.textContent = STYLE_TEXT; document.head.appendChild(styleNode); }); after(() => { document.head.removeChild(styleNode); }); let div: HTMLElement = null!; beforeEach(() => { div = document.createElement('div'); document.body.appendChild(div); }); afterEach(() => { document.body.removeChild(div); }); describe('boxSizing()', () => { it('should return a box sizing with correct values', () => { div.className = 'box-sizing'; let box = ElementExt.boxSizing(div); expect(box.borderTop).to.equal(10); expect(box.borderLeft).to.equal(15); expect(box.borderRight).to.equal(0); expect(box.borderBottom).to.equal(0); expect(box.paddingTop).to.equal(15); expect(box.paddingLeft).to.equal(12); expect(box.paddingRight).to.equal(8); expect(box.paddingBottom).to.equal(9); expect(box.verticalSum).to.equal(34); expect(box.horizontalSum).to.equal(35); }); it('should use defaults if parameters are not set', () => { let sizing = ElementExt.boxSizing(div); expect(sizing.borderTop).to.equal(0); expect(sizing.borderLeft).to.equal(0); expect(sizing.borderRight).to.equal(0); expect(sizing.borderBottom).to.equal(0); expect(sizing.paddingTop).to.equal(0); expect(sizing.paddingLeft).to.equal(0); expect(sizing.paddingRight).to.equal(0); expect(sizing.paddingBottom).to.equal(0); expect(sizing.verticalSum).to.equal(0); expect(sizing.horizontalSum).to.equal(0); }); }); describe('sizeLimits()', () => { it('should return a size limits object with correct parameters', () => { div.className = 'size-limits'; let limits = ElementExt.sizeLimits(div); expect(limits.minWidth).to.equal(90); expect(limits.minHeight).to.equal(95); expect(limits.maxWidth).to.equal(100); expect(limits.maxHeight).to.equal(105); }); it('should use defaults if parameters are not set', () => { let limits = ElementExt.sizeLimits(div); expect(limits.minWidth).to.equal(0); expect(limits.minHeight).to.equal(0); expect(limits.maxWidth).to.equal(Infinity); expect(limits.maxHeight).to.equal(Infinity); }); }); describe('hitTest()', () => { it('should return `true` when point is inside the node', () => { div.className = 'hit-test'; expect(ElementExt.hitTest(div, 50, 50)).to.equal(true); }); it('should return `false` when point is outside the node', () => { div.className = 'hit-test'; expect(ElementExt.hitTest(div, 150, 150)).to.equal(false); }); it('should use closed intervals for left and top only', () => { div.className = 'hit-test'; expect(ElementExt.hitTest(div, 0, 0)).to.equal(true); expect(ElementExt.hitTest(div, 100, 0)).to.equal(false); expect(ElementExt.hitTest(div, 99, 0)).to.equal(true); expect(ElementExt.hitTest(div, 0, 100)).to.equal(false); expect(ElementExt.hitTest(div, 0, 99)).to.equal(true); expect(ElementExt.hitTest(div, 100, 100)).to.equal(false); expect(ElementExt.hitTest(div, 99, 99)).to.equal(true); }); }); describe('scrollIntoViewIfNeeded()', () => { let elemA: HTMLElement = null!; let elemB: HTMLElement = null!; beforeEach(() => { div.className = 'scroll-area'; elemA = document.createElement('div'); elemB = document.createElement('div'); elemA.className = 'scroll-elemA'; elemB.className = 'scroll-elemB'; div.appendChild(elemA); div.appendChild(elemB); }); it('should do nothing if the element covers the viewport', () => { elemB.style.top = '1000px'; div.scrollTop = 75; ElementExt.scrollIntoViewIfNeeded(div, elemA); expect(div.scrollTop).to.equal(75); }); it('should do nothing if the element fits within the viewport', () => { div.scrollTop = 25; ElementExt.scrollIntoViewIfNeeded(div, elemB); expect(div.scrollTop).to.equal(25); }); it('should align the top edge for smaller size elements overlapping the top', () => { elemA.style.top = '1000px'; div.scrollTop = 90; ElementExt.scrollIntoViewIfNeeded(div, elemB); expect(div.scrollTop).to.equal(70); }); it('should align the top edge for equal size elements overlapping the top', () => { elemA.style.height = '600px'; elemB.style.top = '1000px'; div.scrollTop = 90; ElementExt.scrollIntoViewIfNeeded(div, elemA); expect(div.scrollTop).to.equal(50); }); it('should align the top edge for larger size elements overlapping the bottom', () => { elemB.style.top = '1000px'; ElementExt.scrollIntoViewIfNeeded(div, elemA); expect(div.scrollTop).to.equal(50); }); it('should align the top edge for equal size elements overlapping the bottom', () => { elemA.style.height = '600px'; elemB.style.top = '1000px'; ElementExt.scrollIntoViewIfNeeded(div, elemA); expect(div.scrollTop).to.equal(50); }); it('should align the bottom edge for larger size elements overlapping the top', () => { elemB.style.top = '1000px'; div.scrollTop = 200; ElementExt.scrollIntoViewIfNeeded(div, elemA); expect(div.scrollTop).to.equal(150); }); it('should align the bottom edge for smaller size elements overlapping the bottom', () => { elemB.style.top = '600px'; div.scrollTop = 50; ElementExt.scrollIntoViewIfNeeded(div, elemB); expect(div.scrollTop).to.equal(100); }); }); }); }); lumino-2021.12.13/packages/domutils/tests/src/index.spec.ts000066400000000000000000000007671415564225700234120ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import './element.spec'; import './selector.spec'; lumino-2021.12.13/packages/domutils/tests/src/selector.spec.ts000066400000000000000000000141721415564225700241160ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { expect } from 'chai'; import { Selector } from '@lumino/domutils'; describe('@lumino/domutils', () => { describe('Selector', () => { describe('calculateSpecificity()', () => { it('should compute the specificity of a selector', () => { expect(Selector.calculateSpecificity('body')).to.equal(0x000001); expect(Selector.calculateSpecificity('.a-class')).to.equal(0x000100); expect(Selector.calculateSpecificity('#an-id')).to.equal(0x010000); expect(Selector.calculateSpecificity('body.a-class')).to.equal( 0x000101 ); expect(Selector.calculateSpecificity('body#an-id')).to.equal(0x010001); expect(Selector.calculateSpecificity('body:after')).to.equal(0x0000002); expect(Selector.calculateSpecificity('body:first-line')).to.equal( 0x0000002 ); expect(Selector.calculateSpecificity('body::first-line')).to.equal( 0x0000002 ); expect(Selector.calculateSpecificity('body:not(.a-class)')).to.equal( 0x000101 ); expect(Selector.calculateSpecificity('body[foo]')).to.equal(0x000101); expect(Selector.calculateSpecificity('body[foo=bar]')).to.equal( 0x000101 ); expect(Selector.calculateSpecificity('body div')).to.equal(0x0000002); expect(Selector.calculateSpecificity('body .a-class')).to.equal( 0x000101 ); expect(Selector.calculateSpecificity('body #an-id')).to.equal(0x010001); expect( Selector.calculateSpecificity('body div:active::first-letter') ).to.equal(0x000103); expect(Selector.calculateSpecificity('body div > span')).to.equal( 0x000003 ); expect(Selector.calculateSpecificity('.a-class.b-class')).to.equal( 0x000200 ); expect(Selector.calculateSpecificity('.a-class#an-id')).to.equal( 0x010100 ); expect(Selector.calculateSpecificity('.a-class:after')).to.equal( 0x000101 ); expect( Selector.calculateSpecificity('.a-class:not(.b-class)') ).to.equal(0x000200); expect(Selector.calculateSpecificity('.a-class[foo]')).to.equal( 0x000200 ); expect(Selector.calculateSpecificity('.a-class[foo=bar]')).to.equal( 0x000200 ); expect(Selector.calculateSpecificity('.a-class .b-class')).to.equal( 0x000200 ); expect(Selector.calculateSpecificity('.a-class > .b-class')).to.equal( 0x000200 ); expect(Selector.calculateSpecificity('.a-class #an-id')).to.equal( 0x010100 ); expect(Selector.calculateSpecificity('#an-id.a-class')).to.equal( 0x010100 ); expect(Selector.calculateSpecificity('#an-id:after')).to.equal( 0x010001 ); expect(Selector.calculateSpecificity('#an-id:not(.a-class)')).to.equal( 0x010100 ); expect(Selector.calculateSpecificity('#an-id[foo]')).to.equal(0x010100); expect(Selector.calculateSpecificity('#an-id[foo=bar]')).to.equal( 0x010100 ); expect(Selector.calculateSpecificity('#an-id .a-class')).to.equal( 0x010100 ); expect(Selector.calculateSpecificity('#an-id #another-id')).to.equal( 0x020000 ); expect(Selector.calculateSpecificity('#an-id > #another-id')).to.equal( 0x020000 ); expect( Selector.calculateSpecificity('li.thing:nth-child(2)::after') ).to.equal(0x00202); }); }); describe('isValid()', () => { it('returns true if the selector is valid', () => { expect(Selector.isValid('body')).to.equal(true); expect(Selector.isValid('.thing')).to.equal(true); expect(Selector.isValid('#foo')).to.equal(true); expect(Selector.isValid('#bar .thing[foo] > li')).to.equal(true); }); it('returns false if the selector is invalid', () => { expect(Selector.isValid('body {')).to.equal(false); expect(Selector.isValid('.thing<')).to.equal(false); expect(Selector.isValid('4#foo')).to.equal(false); expect(Selector.isValid('(#bar .thing[foo] > li')).to.equal(false); }); }); describe('matches()', () => { const div = document.createElement('div'); div.innerHTML = `
    • Foo Bar
    `; const list = div.firstElementChild!; const item = list.firstElementChild!; const content = item.firstElementChild!; const icon = content.firstElementChild!; const text = icon.nextElementSibling!; it('should return `true` if an element matches a selector', () => { expect(Selector.matches(div, 'div')).to.equal(true); expect(Selector.matches(list, '.list')).to.equal(true); expect(Selector.matches(item, '.list > .item')).to.equal(true); expect(Selector.matches(icon, '.content .icon')).to.equal(true); expect(Selector.matches(text, 'div span + .text')).to.equal(true); }); it('should return `false` if an element does not match a selector', () => { expect(Selector.matches(div, 'li')).to.equal(false); expect(Selector.matches(list, '.content')).to.equal(false); expect(Selector.matches(item, '.content > .item')).to.equal(false); expect(Selector.matches(icon, '.foo .icon')).to.equal(false); expect(Selector.matches(text, 'ol div + .text')).to.equal(false); }); }); }); }); lumino-2021.12.13/packages/domutils/tests/tsconfig.json000066400000000000000000000006721415564225700227150ustar00rootroot00000000000000{ "compilerOptions": { "declaration": false, "noImplicitAny": true, "noEmitOnError": true, "noUnusedLocals": true, "strictNullChecks": true, "module": "commonjs", "moduleResolution": "node", "target": "ES5", "outDir": "build", "lib": ["ES5", "DOM"], "types": ["chai", "mocha"], "rootDir": "src" }, "include": ["src/*"], "references": [ { "path": "../../algorithm" } ] } lumino-2021.12.13/packages/domutils/tests/webpack.config.js000066400000000000000000000003061415564225700234160ustar00rootroot00000000000000var path = require('path'); module.exports = { entry: './build/index.spec.js', mode: 'development', output: { filename: './build/bundle.test.js', path: path.resolve(__dirname) } }; lumino-2021.12.13/packages/domutils/tsconfig.json000066400000000000000000000010711415564225700215450ustar00rootroot00000000000000{ "compilerOptions": { "composite": true, "sourceMap": true, "declaration": true, "declarationDir": "./types", "declarationMap": true, "noImplicitAny": true, "noEmitOnError": true, "noUnusedLocals": true, "strictNullChecks": true, "module": "ES6", "moduleResolution": "node", "target": "ES5", "outDir": "lib", "lib": ["ES5", "DOM", "ES2015.Iterable"], "importHelpers": true, "types": [], "rootDir": "src" }, "include": ["src/*"], "references": [ { "path": "../algorithm" } ] } lumino-2021.12.13/packages/dragdrop/000077500000000000000000000000001415564225700170015ustar00rootroot00000000000000lumino-2021.12.13/packages/dragdrop/api-extractor.json000066400000000000000000000014611415564225700224600ustar00rootroot00000000000000/** * Config file for API Extractor. For more info, please visit: https://api-extractor.com */ { "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", /** * Optionally specifies another JSON config file that this file extends from. This provides a way for * standard settings to be shared across multiple projects. * * If the path starts with "./" or "../", the path is resolved relative to the folder of the file that contains * the "extends" field. Otherwise, the first path segment is interpreted as an NPM package name, and will be * resolved using NodeJS require(). * * SUPPORTED TOKENS: none * DEFAULT VALUE: "" */ "extends": "../../api-extractor-base.json" // "extends": "my-package/include/api-extractor-base.json" } lumino-2021.12.13/packages/dragdrop/package.json000066400000000000000000000055071415564225700212760ustar00rootroot00000000000000{ "name": "@lumino/dragdrop", "version": "1.13.1", "description": "Lumino Drag and Drop", "homepage": "https://github.com/jupyterlab/lumino", "bugs": { "url": "https://github.com/jupyterlab/lumino/issues" }, "repository": { "type": "git", "url": "https://github.com/jupyterlab/lumino.git" }, "license": "BSD-3-Clause", "author": "S. Chris Colbert ", "contributors": [ "A. Darian ", "Dave Willmer ", "S. Chris Colbert ", "Steven Silvester " ], "main": "dist/index.js", "jsdelivr": "dist/index.min.js", "unpkg": "dist/index.min.js", "module": "dist/index.es6", "types": "types/index.d.ts", "style": "style/index.css", "files": [ "dist/*", "src/*", "types/*", "style/*.css", "style/index.js" ], "scripts": { "api": "api-extractor run --local --verbose", "build": "npm run build:src && rollup -c", "build:src": "tsc --build", "build:test": "tsc --build tests && cd tests && webpack", "clean": "rimraf ./lib && rimraf *.tsbuildinfo && rimraf ./types && rimraf ./dist", "clean:test": "rimraf tests/build", "docs": "typedoc --options tdoptions.json src", "minimize": "terser dist/index.js -c -m --source-map \"content='dist/index.js.map',url='index.min.js.map'\" -o dist/index.min.js", "test": "npm run test:firefox-headless", "test:chrome": "cd tests && karma start --browsers=Chrome", "test:chrome-headless": "cd tests && karma start --browsers=ChromeHeadless", "test:firefox": "cd tests && karma start --browsers=Firefox", "test:firefox-headless": "cd tests && karma start --browsers=FirefoxHeadless", "test:ie": "cd tests && karma start --browsers=IE", "watch": "tsc --build --watch" }, "dependencies": { "@lumino/coreutils": "^1.11.1", "@lumino/disposable": "^1.10.1" }, "devDependencies": { "@microsoft/api-extractor": "^7.6.0", "@types/chai": "^3.4.35", "@types/mocha": "^2.2.39", "chai": "^4.3.4", "css-loader": "^3.4.0", "downlevel-dts": "^0.4.0", "es6-promise": "^4.0.5", "karma": "^6.3.4", "karma-chrome-launcher": "^3.1.0", "karma-firefox-launcher": "^2.1.1", "karma-ie-launcher": "^1.0.0", "karma-mocha": "^2.0.1", "karma-mocha-reporter": "^2.2.5", "mocha": "^9.0.3", "rimraf": "^3.0.2", "rollup": "^2.56.0", "rollup-plugin-node-resolve": "^5.2.0", "rollup-plugin-postcss": "^4.0.0", "rollup-plugin-sourcemaps": "^0.6.3", "simulate-event": "^1.4.0", "style-loader": "^1.0.2", "terser": "^5.7.1", "tslib": "^2.3.0", "typedoc": "~0.15.0", "typescript": "~3.6.0", "webpack": "^4.41.3", "webpack-cli": "^3.3.10" }, "publishConfig": { "access": "public" }, "styleModule": "style/index.js" } lumino-2021.12.13/packages/dragdrop/rollup.config.js000066400000000000000000000015231415564225700221210ustar00rootroot00000000000000import nodeResolve from 'rollup-plugin-node-resolve'; import sourcemaps from 'rollup-plugin-sourcemaps'; import postcss from 'rollup-plugin-postcss'; const pkg = require('./package.json'); const globals = id => id.indexOf('@lumino/') === 0 ? id.replace('@lumino/', 'lumino_') : id; export default [ { input: 'lib/index', external: id => pkg.dependencies && !!pkg.dependencies[id], output: [ { file: pkg.main, globals, format: 'umd', sourcemap: true, name: pkg.name }, { file: pkg.module + '.js', format: 'es', sourcemap: true, name: pkg.name } ], plugins: [ nodeResolve({ preferBuiltins: true }), sourcemaps(), postcss({ extensions: ['.css'], minimize: true }) ] } ]; lumino-2021.12.13/packages/dragdrop/src/000077500000000000000000000000001415564225700175705ustar00rootroot00000000000000lumino-2021.12.13/packages/dragdrop/src/index.ts000066400000000000000000001134501415564225700212530ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { MimeData } from '@lumino/coreutils'; import { DisposableDelegate, IDisposable } from '@lumino/disposable'; /** * A type alias which defines the possible independent drop actions. */ export type DropAction = 'none' | 'copy' | 'link' | 'move'; /** * A type alias which defines the possible supported drop actions. */ export type SupportedActions = | DropAction | 'copy-link' | 'copy-move' | 'link-move' | 'all'; /** * A custom event type used for drag-drop operations. * * #### Notes * In order to receive `'lm-dragover'`, `'lm-dragleave'`, or `'lm-drop'` * events, a drop target must cancel the `'lm-dragenter'` event by * calling the event's `preventDefault()` method. */ export interface IDragEvent extends MouseEvent { /** * The drop action supported or taken by the drop target. * * #### Notes * At the start of each event, this value will be `'none'`. During a * `'lm-dragover'` event, the drop target must set this value to one * of the supported actions, or the drop event will not occur. * * When handling the drop event, the drop target should set this * to the action which was *actually* taken. This value will be * reported back to the drag initiator. */ dropAction: DropAction; /** * The drop action proposed by the drag initiator. * * #### Notes * This is the action which is *preferred* by the drag initiator. The * drop target is not required to perform this action, but should if * it all possible. */ readonly proposedAction: DropAction; /** * The drop actions supported by the drag initiator. * * #### Notes * If the `dropAction` is not set to one of the supported actions * during the `'lm-dragover'` event, the drop event will not occur. */ readonly supportedActions: SupportedActions; /** * The mime data associated with the event. * * #### Notes * This is mime data provided by the drag initiator. Drop targets * should use this data to determine if they can handle the drop. */ readonly mimeData: MimeData; /** * The source object of the drag, as provided by the drag initiator. * * #### Notes * For advanced applications, the drag initiator may wish to expose * a source object to the drop targets. That will be provided here * if given by the drag initiator, otherwise it will be `null`. */ readonly source: any; } /** * An object which manages a drag-drop operation. * * A drag object dispatches four different events to drop targets: * * - `'lm-dragenter'` - Dispatched when the mouse enters the target * element. This event must be canceled in order to receive any * of the other events. * * - `'lm-dragover'` - Dispatched when the mouse moves over the drop * target. It must cancel the event and set the `dropAction` to one * of the supported actions in order to receive drop events. * * - `'lm-dragleave'` - Dispatched when the mouse leaves the target * element. This includes moving the mouse into child elements. * * - `'lm-drop'`- Dispatched when the mouse is released over the target * element when the target indicates an appropriate drop action. If * the event is canceled, the indicated drop action is returned to * the initiator through the resolved promise. * * A drag operation can be terminated at any time by pressing `Escape` * or by disposing the drag object. * * A drag object has the ability to automatically scroll a scrollable * element when the mouse is hovered near one of its edges. To enable * this, add the `data-lm-dragscroll` attribute to any element which * the drag object should consider for scrolling. * * #### Notes * This class is designed to be used when dragging and dropping custom * data *within* a single application. It is *not* a replacement for * the native drag-drop API. Instead, it provides an API which allows * drag operations to be initiated programmatically and enables the * transfer of arbitrary non-string objects; features which are not * possible with the native drag-drop API. */ export class Drag implements IDisposable { /** * Construct a new drag object. * * @param options - The options for initializing the drag. */ constructor(options: Drag.IOptions) { this.mimeData = options.mimeData; this.dragImage = options.dragImage || null; this.proposedAction = options.proposedAction || 'copy'; this.supportedActions = options.supportedActions || 'all'; this.source = options.source || null; } /** * Dispose of the resources held by the drag object. * * #### Notes * This will cancel the drag operation if it is active. */ dispose(): void { // Do nothing if the drag object is already disposed. if (this._disposed) { return; } this._disposed = true; // If there is a current target, dispatch a drag leave event. if (this._currentTarget) { let event = Private.createMouseEvent('pointerup', -1, -1); Private.dispatchDragLeave(this, this._currentTarget, null, event); } // Finalize the drag object with `'none'`. this._finalize('none'); } /** * The mime data for the drag object. */ readonly mimeData: MimeData; /** * The drag image element for the drag object. */ readonly dragImage: HTMLElement | null; /** * The proposed drop action for the drag object. */ readonly proposedAction: DropAction; /** * The supported drop actions for the drag object. */ readonly supportedActions: SupportedActions; /** * Get the drag source for the drag object. */ readonly source: any; /** * Test whether the drag object is disposed. */ get isDisposed(): boolean { return this._disposed; } /** * Start the drag operation at the specified client position. * * @param clientX - The client X position for the drag start. * * @param clientY - The client Y position for the drag start. * * @returns A promise which resolves to the result of the drag. * * #### Notes * If the drag has already been started, the promise created by the * first call to `start` is returned. * * If the drag operation has ended, or if the drag object has been * disposed, the returned promise will resolve to `'none'`. * * The drag object will be automatically disposed when drag operation * completes. This means `Drag` objects are for single-use only. * * This method assumes the left mouse button is already held down. */ start(clientX: number, clientY: number): Promise { // If the drag object is already disposed, resolve to `None`. if (this._disposed) { return Promise.resolve('none' as DropAction); } // If the drag has already been started, return the promise. if (this._promise) { return this._promise; } // Install the document listeners for the drag object. this._addListeners(); // Attach the drag image at the specified client position. this._attachDragImage(clientX, clientY); // Create the promise which will be resolved on completion. this._promise = new Promise((resolve, reject) => { this._resolve = resolve; }); // Trigger a fake move event to kick off the drag operation. let event = Private.createMouseEvent('pointermove', clientX, clientY); document.dispatchEvent(event); // Return the pending promise for the drag operation. return this._promise; } /** * Handle the DOM events for the drag operation. * * @param event - The DOM event sent to the drag object. * * #### Notes * This method implements the DOM `EventListener` interface and is * called in response to events on the document. It should not be * called directly by user code. */ handleEvent(event: Event): void { switch (event.type) { case 'mousemove': // this._evtMouseMove(event as MouseEvent); break; case 'mouseup': // this._evtMouseUp(event as MouseEvent); break; case 'pointermove': this._evtMouseMove(event as MouseEvent); break; case 'pointerup': this._evtMouseUp(event as MouseEvent); break; case 'keydown': this._evtKeyDown(event as KeyboardEvent); break; default: // Stop all other events during drag-drop. event.preventDefault(); event.stopPropagation(); break; } } /** * Move the drag image element to the specified location. * * This is a no-op if there is no drag image element. */ protected moveDragImage(clientX: number, clientY: number): void { if (!this.dragImage) { return; } let style = this.dragImage.style; style.top = `${clientY}px`; style.left = `${clientX}px`; } /** * Handle the `'mousemove'` event for the drag object. */ private _evtMouseMove(event: MouseEvent): void { // Stop all input events during drag-drop. event.preventDefault(); event.stopPropagation(); // Update the current target node and dispatch enter/leave events. this._updateCurrentTarget(event); // Update the drag scroll element. this._updateDragScroll(event); // Move the drag image to the specified client position. This is // performed *after* dispatching to prevent unnecessary reflows. this.moveDragImage(event.clientX, event.clientY); } /** * Handle the `'mouseup'` event for the drag object. */ private _evtMouseUp(event: MouseEvent): void { // Stop all input events during drag-drop. event.preventDefault(); event.stopPropagation(); // Do nothing if the left button is not released. if (event.button !== 0) { return; } // Update the current target node and dispatch enter/leave events. // This prevents a subtle issue where the DOM mutates under the // cursor after the last move event but before the drop event. this._updateCurrentTarget(event); // If there is no current target, finalize with `'none'`. if (!this._currentTarget) { this._finalize('none'); return; } // If the last drop action was `'none'`, dispatch a leave event // to the current target and finalize the drag with `'none'`. if (this._dropAction === 'none') { Private.dispatchDragLeave(this, this._currentTarget, null, event); this._finalize('none'); return; } // Dispatch the drop event at the current target and finalize // with the resulting drop action. let action = Private.dispatchDrop(this, this._currentTarget, event); this._finalize(action); } /** * Handle the `'keydown'` event for the drag object. */ private _evtKeyDown(event: KeyboardEvent): void { // Stop all input events during drag-drop. event.preventDefault(); event.stopPropagation(); // Cancel the drag if `Escape` is pressed. if (event.keyCode === 27) { this.dispose(); } } /** * Add the document event listeners for the drag object. */ private _addListeners(): void { document.addEventListener('mousedown', this, true); // document.addEventListener('mousemove', this, true); // document.addEventListener('mouseup', this, true); // document.addEventListener('mouseenter', this, true); // document.addEventListener('mouseleave', this, true); // document.addEventListener('mouseover', this, true); // document.addEventListener('mouseout', this, true); // document.addEventListener('pointerdown', this, true); document.addEventListener('pointermove', this, true); document.addEventListener('pointerup', this, true); document.addEventListener('pointerenter', this, true); document.addEventListener('pointerleave', this, true); document.addEventListener('pointerover', this, true); document.addEventListener('pointerout', this, true); document.addEventListener('keydown', this, true); document.addEventListener('keyup', this, true); document.addEventListener('keypress', this, true); document.addEventListener('contextmenu', this, true); } /** * Remove the document event listeners for the drag object. */ private _removeListeners(): void { document.removeEventListener('mousedown', this, true); // document.removeEventListener('mousemove', this, true); // document.removeEventListener('mouseup', this, true); // document.removeEventListener('mouseenter', this, true); // document.removeEventListener('mouseleave', this, true); // document.removeEventListener('mouseover', this, true); // document.removeEventListener('mouseout', this, true); // document.removeEventListener('pointerdown', this, true); document.removeEventListener('pointermove', this, true); document.removeEventListener('pointerup', this, true); document.removeEventListener('pointerenter', this, true); document.removeEventListener('pointerleave', this, true); document.removeEventListener('pointerover', this, true); document.removeEventListener('pointerout', this, true); document.removeEventListener('keydown', this, true); document.removeEventListener('keyup', this, true); document.removeEventListener('keypress', this, true); document.removeEventListener('contextmenu', this, true); } /** * Update the drag scroll element under the mouse. */ private _updateDragScroll(event: MouseEvent): void { // Find the scroll target under the mouse. let target = Private.findScrollTarget(event); // Bail if there is nothing to scroll. if (!this._scrollTarget && !target) { return; } // Start the scroll loop if needed. if (!this._scrollTarget) { setTimeout(this._onScrollFrame, 500); } // Update the scroll target. this._scrollTarget = target; } /** * Update the current target node using the given mouse event. */ private _updateCurrentTarget(event: MouseEvent): void { // Fetch common local state. let prevTarget = this._currentTarget; let currTarget = this._currentTarget; let prevElem = this._currentElement; // Find the current indicated element at the given position. let currElem = document.elementFromPoint(event.clientX, event.clientY); // Update the current element reference. this._currentElement = currElem; // If the indicated element changes from the previous iteration, // and is different from the current target, dispatch the exit // event to the target. if (currElem !== prevElem && currElem !== currTarget) { Private.dispatchDragExit(this, currTarget, currElem, event); } // If the indicated element changes from the previous iteration, // and is different from the current target, dispatch the enter // event and compute the new target element. if (currElem !== prevElem && currElem !== currTarget) { currTarget = Private.dispatchDragEnter(this, currElem, currTarget, event); } // If the current target element has changed, update the current // target reference and dispatch the leave event to the old target. if (currTarget !== prevTarget) { this._currentTarget = currTarget; Private.dispatchDragLeave(this, prevTarget, currTarget, event); } // Dispatch the drag over event and update the drop action. let action = Private.dispatchDragOver(this, currTarget, event); this._setDropAction(action); } /** * Attach the drag image element at the specified location. * * This is a no-op if there is no drag image element. */ private _attachDragImage(clientX: number, clientY: number): void { if (!this.dragImage) { return; } this.dragImage.classList.add('lm-mod-drag-image'); /* */ this.dragImage.classList.add('p-mod-drag-image'); /* */ let style = this.dragImage.style; style.pointerEvents = 'none'; style.position = 'fixed'; style.top = `${clientY}px`; style.left = `${clientX}px`; document.body.appendChild(this.dragImage); } /** * Detach the drag image element from the DOM. * * This is a no-op if there is no drag image element. */ private _detachDragImage(): void { if (!this.dragImage) { return; } let parent = this.dragImage.parentNode; if (!parent) { return; } parent.removeChild(this.dragImage); } /** * Set the internal drop action state and update the drag cursor. */ private _setDropAction(action: DropAction): void { action = Private.validateAction(action, this.supportedActions); if (this._override && this._dropAction === action) { return; } switch (action) { case 'none': this._dropAction = action; this._override = Drag.overrideCursor('no-drop'); break; case 'copy': this._dropAction = action; this._override = Drag.overrideCursor('copy'); break; case 'link': this._dropAction = action; this._override = Drag.overrideCursor('alias'); break; case 'move': this._dropAction = action; this._override = Drag.overrideCursor('move'); break; } } /** * Finalize the drag operation and resolve the drag promise. */ private _finalize(action: DropAction): void { // Store the resolve function as a temp variable. let resolve = this._resolve; // Remove the document event listeners. this._removeListeners(); // Detach the drag image. this._detachDragImage(); // Dispose of the cursor override. if (this._override) { this._override.dispose(); this._override = null; } // Clear the mime data. this.mimeData.clear(); // Clear the rest of the internal drag state. this._disposed = true; this._dropAction = 'none'; this._currentTarget = null; this._currentElement = null; this._scrollTarget = null; this._promise = null; this._resolve = null; // Finally, resolve the promise to the given drop action. if (resolve) { resolve(action); } } /** * The scroll loop handler function. */ private _onScrollFrame = () => { // Bail early if there is no scroll target. if (!this._scrollTarget) { return; } // Unpack the scroll target. let { element, edge, distance } = this._scrollTarget; // Calculate the scroll delta using nonlinear acceleration. let d = Private.SCROLL_EDGE_SIZE - distance; let f = Math.pow(d / Private.SCROLL_EDGE_SIZE, 2); let s = Math.max(1, Math.round(f * Private.SCROLL_EDGE_SIZE)); // Scroll the element in the specified direction. switch (edge) { case 'top': element.scrollTop -= s; break; case 'left': element.scrollLeft -= s; break; case 'right': element.scrollLeft += s; break; case 'bottom': element.scrollTop += s; break; } // Request the next cycle of the scroll loop. requestAnimationFrame(this._onScrollFrame); }; private _disposed = false; private _dropAction: DropAction = 'none'; private _override: IDisposable | null = null; private _currentTarget: Element | null = null; private _currentElement: Element | null = null; private _promise: Promise | null = null; private _scrollTarget: Private.IScrollTarget | null = null; private _resolve: ((value: DropAction) => void) | null = null; } /** * The namespace for the `Drag` class statics. */ export namespace Drag { /** * An options object for initializing a `Drag` object. */ export interface IOptions { /** * The populated mime data for the drag operation. */ mimeData: MimeData; /** * An optional drag image which follows the mouse cursor. * * #### Notes * The drag image can be any DOM element. It is not limited to * image or canvas elements as with the native drag-drop APIs. * * If provided, this will be positioned at the mouse cursor on each * mouse move event. A CSS transform can be used to offset the node * from its specified position. * * The drag image will automatically have the `lm-mod-drag-image` * class name added. * * The default value is `null`. */ dragImage?: HTMLElement; /** * The optional proposed drop action for the drag operation. * * #### Notes * This can be provided as a hint to the drop targets as to which * drop action is preferred. * * The default value is `'copy'`. */ proposedAction?: DropAction; /** * The drop actions supported by the drag initiator. * * #### Notes * A drop target must indicate that it intends to perform one of the * supported actions in order to receive a drop event. However, it is * not required to *actually* perform that action when handling the * drop event. Therefore, the initiator must be prepared to handle * any drop action performed by a drop target. * * The default value is `'all'`. */ supportedActions?: SupportedActions; /** * An optional object which indicates the source of the drag. * * #### Notes * For advanced applications, the drag initiator may wish to expose * a source object to the drop targets. That object can be specified * here and will be carried along with the drag events. * * The default value is `null`. */ source?: any; } /** * Override the cursor icon for the entire document. * * @param cursor - The string representing the cursor style. * * @returns A disposable which will clear the override when disposed. * * #### Notes * The most recent call to `overrideCursor` takes precedence. * Disposing an old override has no effect on the current override. * * This utility function is used by the `Drag` class to override the * mouse cursor during a drag-drop operation, but it can also be used * by other classes to fix the cursor icon during normal mouse drags. * * #### Example * ```typescript * import { Drag } from '@lumino/dragdrop'; * * // Force the cursor to be 'wait' for the entire document. * let override = Drag.overrideCursor('wait'); * * // Clear the override by disposing the return value. * override.dispose(); * ``` */ export function overrideCursor(cursor: string): IDisposable { let id = ++overrideCursorID; document.body.style.cursor = cursor; document.body.classList.add('lm-mod-override-cursor'); /* */ document.body.classList.add('p-mod-override-cursor'); /* */ return new DisposableDelegate(() => { if (id === overrideCursorID) { document.body.style.cursor = ''; document.body.classList.remove('lm-mod-override-cursor'); /* */ document.body.classList.remove('p-mod-override-cursor'); /* */ } }); } /** * The internal id for the active cursor override. */ let overrideCursorID = 0; } /** * The namespace for the module implementation details. */ namespace Private { /** * The size of a drag scroll edge, in pixels. */ export const SCROLL_EDGE_SIZE = 20; /** * Validate the given action is one of the supported actions. * * Returns the given action or `'none'` if the action is unsupported. */ export function validateAction( action: DropAction, supported: SupportedActions ): DropAction { return actionTable[action] & supportedTable[supported] ? action : 'none'; } /** * Create a left mouse event at the given position. * * @param type - The event type for the mouse event. * * @param clientX - The client X position. * * @param clientY - The client Y position. * * @returns A newly created and initialized mouse event. */ export function createMouseEvent( type: string, clientX: number, clientY: number ): MouseEvent { let event = document.createEvent('MouseEvent'); event.initMouseEvent( type, true, true, window, 0, 0, 0, clientX, clientY, false, false, false, false, 0, null ); return event; } /** * An object which holds the scroll target data. */ export interface IScrollTarget { /** * The element to be scrolled. */ element: Element; /** * The scroll edge underneath the mouse. */ edge: 'top' | 'left' | 'right' | 'bottom'; /** * The distance from the mouse to the scroll edge. */ distance: number; } /** * Find the drag scroll target under the mouse, if any. */ export function findScrollTarget(event: MouseEvent): IScrollTarget | null { // Look up the client mouse position. let x = event.clientX; let y = event.clientY; // Get the element under the mouse. let element: Element | null = document.elementFromPoint(x, y); // Search for a scrollable target based on the mouse position. // The null assert in third clause of for-loop is required due to: // https://github.com/Microsoft/TypeScript/issues/14143 for (; element; element = element!.parentElement) { // Ignore elements which are not marked as scrollable. let scrollable = element.hasAttribute('data-lm-dragscroll'); /* */ scrollable = scrollable || element.hasAttribute('data-p-dragscroll'); /* */ if (!scrollable) { continue; } // Set up the coordinate offsets for the element. let offsetX = 0; let offsetY = 0; if (element === document.body) { offsetX = window.pageXOffset; offsetY = window.pageYOffset; } // Get the element bounds in viewport coordinates. let r = element.getBoundingClientRect(); let top = r.top + offsetY; let left = r.left + offsetX; let right = left + r.width; let bottom = top + r.height; // Skip the element if it's not under the mouse. if (x < left || x >= right || y < top || y >= bottom) { continue; } // Compute the distance to each edge. let dl = x - left + 1; let dt = y - top + 1; let dr = right - x; let db = bottom - y; // Find the smallest of the edge distances. let distance = Math.min(dl, dt, dr, db); // Skip the element if the mouse is not within a scroll edge. if (distance > SCROLL_EDGE_SIZE) { continue; } // Set up the edge result variable. let edge: 'top' | 'left' | 'right' | 'bottom'; // Find the edge for the computed distance. switch (distance) { case db: edge = 'bottom'; break; case dt: edge = 'top'; break; case dr: edge = 'right'; break; case dl: edge = 'left'; break; default: throw 'unreachable'; } // Compute how much the element can scroll in width and height. let dsw = element.scrollWidth - element.clientWidth; let dsh = element.scrollHeight - element.clientHeight; // Determine if the element should be scrolled for the edge. let shouldScroll: boolean; switch (edge) { case 'top': shouldScroll = dsh > 0 && element.scrollTop > 0; break; case 'left': shouldScroll = dsw > 0 && element.scrollLeft > 0; break; case 'right': shouldScroll = dsw > 0 && element.scrollLeft < dsw; break; case 'bottom': shouldScroll = dsh > 0 && element.scrollTop < dsh; break; default: throw 'unreachable'; } // Skip the element if it should not be scrolled. if (!shouldScroll) { continue; } // Return the drag scroll target. return { element, edge, distance }; } // No drag scroll target was found. return null; } /** * Dispatch a drag enter event to the indicated element. * * @param drag - The drag object associated with the action. * * @param currElem - The currently indicated element, or `null`. This * is the "immediate user selection" from the whatwg spec. * * @param currTarget - The current drag target element, or `null`. This * is the "current target element" from the whatwg spec. * * @param event - The mouse event related to the action. * * @returns The element to use as the current drag target. This is the * "current target element" from the whatwg spec, and may be `null`. * * #### Notes * This largely implements the drag enter portion of the whatwg spec: * https://html.spec.whatwg.org/multipage/interaction.html#drag-and-drop-processing-model */ export function dispatchDragEnter( drag: Drag, currElem: Element | null, currTarget: Element | null, event: MouseEvent ): Element | null { // If the current element is null, return null as the new target. if (!currElem) { return null; } // Dispatch a drag enter event to the current element. let dragEvent = createDragEvent('lm-dragenter', drag, event, currTarget); let canceled = !currElem.dispatchEvent(dragEvent); // If the event was canceled, use the current element as the new target. if (canceled) { return currElem; } /* */ dragEvent = createDragEvent('p-dragenter', drag, event, currTarget); canceled = !currElem.dispatchEvent(dragEvent); if (canceled) { return currElem; } /* */ // If the current element is the document body, keep the original target. if (currElem === document.body) { return currTarget; } // Dispatch a drag enter event on the document body. dragEvent = createDragEvent('lm-dragenter', drag, event, currTarget); document.body.dispatchEvent(dragEvent); /* */ dragEvent = createDragEvent('p-dragenter', drag, event, currTarget); document.body.dispatchEvent(dragEvent); /* */ // Ignore the event cancellation, and use the body as the new target. return document.body; } /** * Dispatch a drag exit event to the indicated element. * * @param drag - The drag object associated with the action. * * @param prevTarget - The previous target element, or `null`. This * is the previous "current target element" from the whatwg spec. * * @param currTarget - The current drag target element, or `null`. This * is the "current target element" from the whatwg spec. * * @param event - The mouse event related to the action. * * #### Notes * This largely implements the drag exit portion of the whatwg spec: * https://html.spec.whatwg.org/multipage/interaction.html#drag-and-drop-processing-model */ export function dispatchDragExit( drag: Drag, prevTarget: Element | null, currTarget: Element | null, event: MouseEvent ): void { // If the previous target is null, do nothing. if (!prevTarget) { return; } // Dispatch the drag exit event to the previous target. let dragEvent = createDragEvent('lm-dragexit', drag, event, currTarget); prevTarget.dispatchEvent(dragEvent); /* */ dragEvent = createDragEvent('p-dragexit', drag, event, currTarget); prevTarget.dispatchEvent(dragEvent); /* */ } /** * Dispatch a drag leave event to the indicated element. * * @param drag - The drag object associated with the action. * * @param prevTarget - The previous target element, or `null`. This * is the previous "current target element" from the whatwg spec. * * @param currTarget - The current drag target element, or `null`. This * is the "current target element" from the whatwg spec. * * @param event - The mouse event related to the action. * * #### Notes * This largely implements the drag leave portion of the whatwg spec: * https://html.spec.whatwg.org/multipage/interaction.html#drag-and-drop-processing-model */ export function dispatchDragLeave( drag: Drag, prevTarget: Element | null, currTarget: Element | null, event: MouseEvent ): void { // If the previous target is null, do nothing. if (!prevTarget) { return; } // Dispatch the drag leave event to the previous target. let dragEvent = createDragEvent('lm-dragleave', drag, event, currTarget); prevTarget.dispatchEvent(dragEvent); /* */ dragEvent = createDragEvent('p-dragleave', drag, event, currTarget); prevTarget.dispatchEvent(dragEvent); /* */ } /** * Dispatch a drag over event to the indicated element. * * @param drag - The drag object associated with the action. * * @param currTarget - The current drag target element, or `null`. This * is the "current target element" from the whatwg spec. * * @param event - The mouse event related to the action. * * @returns The `DropAction` result of the drag over event. * * #### Notes * This largely implements the drag over portion of the whatwg spec: * https://html.spec.whatwg.org/multipage/interaction.html#drag-and-drop-processing-model */ export function dispatchDragOver( drag: Drag, currTarget: Element | null, event: MouseEvent ): DropAction { // If there is no current target, the drop action is none. if (!currTarget) { return 'none'; } // Dispatch the drag over event to the current target. let dragEvent = createDragEvent('lm-dragover', drag, event, null); let canceled = !currTarget.dispatchEvent(dragEvent); // If the event was canceled, return the drop action result. if (canceled) { return dragEvent.dropAction; } /* */ dragEvent = createDragEvent('p-dragover', drag, event, null); canceled = !currTarget.dispatchEvent(dragEvent); if (canceled) { return dragEvent.dropAction; } /* */ // Otherwise, the effective drop action is none. return 'none'; } /** * Dispatch a drop event to the indicated element. * * @param drag - The drag object associated with the action. * * @param currTarget - The current drag target element, or `null`. This * is the "current target element" from the whatwg spec. * * @param event - The mouse event related to the action. * * @returns The `DropAction` result of the drop event. * * #### Notes * This largely implements the drag over portion of the whatwg spec: * https://html.spec.whatwg.org/multipage/interaction.html#drag-and-drop-processing-model */ export function dispatchDrop( drag: Drag, currTarget: Element | null, event: MouseEvent ): DropAction { // If there is no current target, the drop action is none. if (!currTarget) { return 'none'; } // Dispatch the drop event to the current target. let dragEvent = createDragEvent('lm-drop', drag, event, null); let canceled = !currTarget.dispatchEvent(dragEvent); // If the event was canceled, return the drop action result. if (canceled) { return dragEvent.dropAction; } /* */ dragEvent = createDragEvent('p-drop', drag, event, null); canceled = !currTarget.dispatchEvent(dragEvent); if (canceled) { return dragEvent.dropAction; } /* */ // Otherwise, the effective drop action is none. return 'none'; } /** * A lookup table from drop action to bit value. */ const actionTable: { [key: string]: number } = { none: 0x0, copy: 0x1, link: 0x2, move: 0x4 }; /** * A lookup table from supported action to drop action bit mask. */ const supportedTable: { [key: string]: number } = { none: actionTable['none'], copy: actionTable['copy'], link: actionTable['link'], move: actionTable['move'], 'copy-link': actionTable['copy'] | actionTable['link'], 'copy-move': actionTable['copy'] | actionTable['move'], 'link-move': actionTable['link'] | actionTable['move'], all: actionTable['copy'] | actionTable['link'] | actionTable['move'] }; /** * Create a new initialized `IDragEvent` from the given data. * * @param type - The event type for the drag event. * * @param drag - The drag object to use for seeding the drag data. * * @param event - The mouse event to use for seeding the mouse data. * * @param related - The related target for the event, or `null`. * * @returns A new object which implements `IDragEvent`. */ function createDragEvent( type: string, drag: Drag, event: MouseEvent, related: Element | null ): IDragEvent { // Create a new mouse event to use as the drag event. Currently, // JS engines do now allow user-defined Event subclasses. let dragEvent = document.createEvent('MouseEvent'); // Initialize the mouse event data. dragEvent.initMouseEvent( type, true, true, window, 0, event.screenX, event.screenY, event.clientX, event.clientY, event.ctrlKey, event.altKey, event.shiftKey, event.metaKey, event.button, related ); // Forcefully add the custom drag event properties. (dragEvent as any).dropAction = 'none'; (dragEvent as any).mimeData = drag.mimeData; (dragEvent as any).proposedAction = drag.proposedAction; (dragEvent as any).supportedActions = drag.supportedActions; (dragEvent as any).source = drag.source; // Return the fully initialized drag event. return dragEvent as IDragEvent; } } lumino-2021.12.13/packages/dragdrop/style/000077500000000000000000000000001415564225700201415ustar00rootroot00000000000000lumino-2021.12.13/packages/dragdrop/style/index.css000066400000000000000000000010151415564225700217570ustar00rootroot00000000000000/*----------------------------------------------------------------------------- | Copyright (c) Jupyter Development Team. | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ /* */ body.p-mod-override-cursor *, /* */ body.lm-mod-override-cursor * { cursor: inherit !important; } lumino-2021.12.13/packages/dragdrop/style/index.js000066400000000000000000000006361415564225700216130ustar00rootroot00000000000000/*----------------------------------------------------------------------------- | Copyright (c) Jupyter Development Team. | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import './index.css'; lumino-2021.12.13/packages/dragdrop/tdoptions.json000066400000000000000000000005171415564225700217220ustar00rootroot00000000000000{ "excludeNotExported": true, "mode": "file", "target": "es5", "module": "es5", "lib": [ "lib.es2015.d.ts", "lib.es2015.collection.d.ts", "lib.es2015.promise.d.ts", "lib.dom.d.ts" ], "out": "../../docs/source/api/dragdrop", "baseUrl": ".", "paths": { "@lumino/*": ["node_modules/@lumino/*"] } } lumino-2021.12.13/packages/dragdrop/tests/000077500000000000000000000000001415564225700201435ustar00rootroot00000000000000lumino-2021.12.13/packages/dragdrop/tests/karma.conf.js000066400000000000000000000005051415564225700225200ustar00rootroot00000000000000module.exports = function (config) { config.set({ basePath: '.', frameworks: ['mocha'], reporters: ['mocha'], files: ['build/bundle.test.js'], port: 9876, colors: true, singleRun: true, browserNoActivityTimeout: 30000, failOnEmptyTestSuite: false, logLevel: config.LOG_INFO }); }; lumino-2021.12.13/packages/dragdrop/tests/src/000077500000000000000000000000001415564225700207325ustar00rootroot00000000000000lumino-2021.12.13/packages/dragdrop/tests/src/index.spec.ts000066400000000000000000000507151415564225700233520ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import 'es6-promise/auto'; // polyfill Promise on IE import { expect } from 'chai'; import { generate, simulate } from 'simulate-event'; import { MimeData } from '@lumino/coreutils'; import { Drag, IDragEvent } from '@lumino/dragdrop'; import '@lumino/dragdrop/style/index.css'; class DropTarget { node = document.createElement('div'); events: string[] = []; constructor() { this.node.style.minWidth = '100px'; this.node.style.minHeight = '100px'; this.node.addEventListener('lm-dragenter', this); this.node.addEventListener('lm-dragover', this); this.node.addEventListener('lm-dragleave', this); this.node.addEventListener('lm-drop', this); document.body.appendChild(this.node); } dispose(): void { document.body.removeChild(this.node); this.node.removeEventListener('lm-dragenter', this); this.node.removeEventListener('lm-dragover', this); this.node.removeEventListener('lm-dragleave', this); this.node.removeEventListener('lm-drop', this); } handleEvent(event: Event): void { this.events.push(event.type); switch (event.type) { case 'lm-dragenter': this._evtDragEnter(event as IDragEvent); break; case 'lm-dragleave': this._evtDragLeave(event as IDragEvent); break; case 'lm-dragover': this._evtDragOver(event as IDragEvent); break; case 'lm-drop': this._evtDrop(event as IDragEvent); break; } } private _evtDragEnter(event: IDragEvent): void { event.preventDefault(); event.stopPropagation(); } private _evtDragLeave(event: IDragEvent): void { event.preventDefault(); event.stopPropagation(); } private _evtDragOver(event: IDragEvent): void { event.preventDefault(); event.stopPropagation(); event.dropAction = event.proposedAction; } private _evtDrop(event: IDragEvent): void { event.preventDefault(); event.stopPropagation(); if (event.proposedAction === 'none') { event.dropAction = 'none'; return; } event.dropAction = event.proposedAction; } } describe('@lumino/dragdrop', () => { describe('Drag', () => { describe('#constructor()', () => { it('should accept an options object', () => { let drag = new Drag({ mimeData: new MimeData() }); expect(drag).to.be.an.instanceof(Drag); }); it('should accept optional options', () => { let dragImage = document.createElement('i'); let source = {}; let mimeData = new MimeData(); let drag = new Drag({ mimeData, dragImage, proposedAction: 'copy', supportedActions: 'copy-link', source }); expect(drag).to.be.an.instanceof(Drag); expect(drag.mimeData).to.equal(mimeData); expect(drag.dragImage).to.equal(dragImage); expect(drag.proposedAction).to.equal('copy'); expect(drag.supportedActions).to.equal('copy-link'); expect(drag.source).to.equal(source); }); }); describe('#dispose()', () => { it('should dispose of the resources held by the drag object', () => { let drag = new Drag({ mimeData: new MimeData() }); drag.dispose(); expect(drag.isDisposed).to.equal(true); }); it('should cancel the drag operation if it is active', done => { let drag = new Drag({ mimeData: new MimeData() }); drag.start(0, 0).then(action => { expect(action).to.equal('none'); done(); }); drag.dispose(); }); it('should be a no-op if already disposed', () => { let drag = new Drag({ mimeData: new MimeData() }); drag.dispose(); drag.dispose(); expect(drag.isDisposed).to.equal(true); }); }); describe('#isDisposed()', () => { it('should test whether the drag object is disposed', () => { let drag = new Drag({ mimeData: new MimeData() }); expect(drag.isDisposed).to.equal(false); drag.dispose(); expect(drag.isDisposed).to.equal(true); }); }); describe('#mimeData', () => { it('should get the mime data for the drag object', () => { let mimeData = new MimeData(); let drag = new Drag({ mimeData }); expect(drag.mimeData).to.equal(mimeData); }); }); describe('#dragImage', () => { it('should get the drag image element for the drag object', () => { let dragImage = document.createElement('i'); let drag = new Drag({ mimeData: new MimeData(), dragImage }); expect(drag.dragImage).to.equal(dragImage); }); it('should default to `null`', () => { let drag = new Drag({ mimeData: new MimeData() }); expect(drag.dragImage).to.equal(null); }); }); describe('#proposedAction', () => { it('should get the proposed drop action for the drag object', () => { let drag = new Drag({ mimeData: new MimeData(), proposedAction: 'link' }); expect(drag.proposedAction).to.equal('link'); }); it("should default to `'copy'`", () => { let drag = new Drag({ mimeData: new MimeData() }); expect(drag.proposedAction).to.equal('copy'); }); }); describe('#supportedActions', () => { it('should get the supported drop actions for the drag object', () => { let drag = new Drag({ mimeData: new MimeData(), supportedActions: 'copy-move' }); expect(drag.supportedActions).to.equal('copy-move'); }); it("should default to `'all'`", () => { let drag = new Drag({ mimeData: new MimeData() }); expect(drag.supportedActions).to.equal('all'); }); }); describe('#source', () => { it('should get the drag source for the drag object', () => { let source = {}; let drag = new Drag({ mimeData: new MimeData(), source }); expect(drag.source).to.equal(source); }); it('should default to `null`', () => { let drag = new Drag({ mimeData: new MimeData() }); expect(drag.source).to.equal(null); }); }); describe('#start()', () => { it('should start the drag operation at the specified client position', () => { let dragImage = document.createElement('span'); dragImage.style.minHeight = '10px'; dragImage.style.minWidth = '10px'; let drag = new Drag({ mimeData: new MimeData(), dragImage }); drag.start(10, 20); expect(dragImage.style.top).to.equal('20px'); expect(dragImage.style.left).to.equal('10px'); drag.dispose(); }); it('should return a previous promise if a drag has already been started', () => { let drag = new Drag({ mimeData: new MimeData() }); let promise = drag.start(0, 0); expect(drag.start(10, 10)).to.equal(promise); drag.dispose(); }); it("should resolve to `'none'` if the drag operation has been disposed", done => { let drag = new Drag({ mimeData: new MimeData() }); drag.start(0, 0).then(action => { expect(action).to.equal('none'); done(); }); drag.dispose(); }); }); context('Event Handling', () => { let drag: Drag = null!; let child0: DropTarget = null!; let child1: DropTarget = null!; beforeEach(() => { child0 = new DropTarget(); child1 = new DropTarget(); let dragImage = document.createElement('div'); dragImage.style.minHeight = '10px'; dragImage.style.minWidth = '10px'; drag = new Drag({ mimeData: new MimeData(), dragImage }); drag.start(0, 0); }); afterEach(() => { drag.dispose(); child0.dispose(); child1.dispose(); }); describe('mousemove', () => { it('should be prevented during a drag event', () => { let evt = generate('mousemove'); let canceled = !document.body.dispatchEvent(evt); expect(canceled).to.equal(true); }); it('should dispatch an enter and leave events', () => { let rect = child0.node.getBoundingClientRect(); simulate(child0.node, 'mousemove', { clientX: rect.left + 1, clientY: rect.top + 1 }); expect(child0.events).to.contain('lm-dragenter'); child0.events = []; rect = child1.node.getBoundingClientRect(); simulate(child1.node, 'mousemove', { clientX: rect.left + 1, clientY: rect.top + 1 }); expect(child0.events).to.contain('lm-dragleave'); expect(child1.events).to.contain('lm-dragenter'); }); it('should dispatch drag over event', () => { let rect = child0.node.getBoundingClientRect(); simulate(child0.node, 'mousemove', { clientX: rect.left + 1, clientY: rect.top + 1 }); expect(child0.events).to.contain('lm-dragover'); }); it('should move the drag image to the client location', () => { let rect = child0.node.getBoundingClientRect(); simulate(child0.node, 'mousemove', { clientX: rect.left + 1, clientY: rect.top + 1 }); let image = drag.dragImage!; expect(image.style.top).to.equal(`${rect.top + 1}px`); expect(image.style.left).to.equal(`${rect.left + 1}px`); }); }); describe('mouseup', () => { it('should be prevented during a drag event', () => { let evt = generate('mouseup'); let canceled = !document.body.dispatchEvent(evt); expect(canceled).to.equal(true); }); it('should do nothing if the left button is not released', () => { let rect = child0.node.getBoundingClientRect(); simulate(child0.node, 'mouseup', { clientX: rect.left + 1, clientY: rect.top + 1, button: 1 }); expect(child0.events).to.not.contain('lm-dragenter'); }); it('should dispatch enter and leave events', () => { let rect = child0.node.getBoundingClientRect(); simulate(child0.node, 'mousemove', { clientX: rect.left + 1, clientY: rect.top + 1 }); expect(child0.events).to.contain('lm-dragenter'); child0.events = []; rect = child1.node.getBoundingClientRect(); simulate(child1.node, 'mouseup', { clientX: rect.left + 1, clientY: rect.top + 1 }); expect(child0.events).to.contain('lm-dragleave'); expect(child1.events).to.contain('lm-dragenter'); }); it("should dispatch a leave event if the last drop action was `'none'", () => { drag.dispose(); drag = new Drag({ mimeData: new MimeData(), supportedActions: 'none' }); drag.start(0, 0); let rect = child0.node.getBoundingClientRect(); simulate(child0.node, 'mouseup', { clientX: rect.left + 1, clientY: rect.top + 1 }); expect(child0.events).to.contain('lm-dragleave'); }); it("should finalize the drag with `'none' if the last drop action was `'none`", done => { drag.dispose(); drag = new Drag({ mimeData: new MimeData(), supportedActions: 'none' }); drag.start(0, 0).then(action => { expect(action).to.equal('none'); done(); }); let rect = child0.node.getBoundingClientRect(); simulate(child0.node, 'mouseup', { clientX: rect.left + 1, clientY: rect.top + 1 }); }); it('should dispatch the drop event at the current target', () => { let rect = child0.node.getBoundingClientRect(); simulate(child0.node, 'mouseup', { clientX: rect.left + 1, clientY: rect.top + 1 }); expect(child0.events).to.contain('lm-drop'); }); it('should resolve with the drop action', done => { drag.dispose(); drag = new Drag({ mimeData: new MimeData(), proposedAction: 'link', supportedActions: 'link' }); drag.start(0, 0).then(action => { expect(action).to.equal('link'); done(); }); let rect = child0.node.getBoundingClientRect(); simulate(child0.node, 'mouseup', { clientX: rect.left + 1, clientY: rect.top + 1 }); }); it('should handle a `move` action', done => { drag.dispose(); drag = new Drag({ mimeData: new MimeData(), proposedAction: 'move', supportedActions: 'copy-move' }); drag.start(0, 0).then(action => { expect(action).to.equal('move'); done(); }); let rect = child0.node.getBoundingClientRect(); simulate(child0.node, 'mouseup', { clientX: rect.left + 1, clientY: rect.top + 1 }); }); it('should dispose of the drop', () => { let rect = child0.node.getBoundingClientRect(); simulate(child0.node, 'mouseup', { clientX: rect.left + 1, clientY: rect.top + 1 }); expect(drag.isDisposed).to.equal(true); }); it('should detach the drag image', () => { let image = drag.dragImage!; let rect = child0.node.getBoundingClientRect(); simulate(child0.node, 'mouseup', { clientX: rect.left + 1, clientY: rect.top + 1 }); expect(document.body.contains(image)).to.equal(false); }); it('should remove event listeners', () => { let rect = child0.node.getBoundingClientRect(); simulate(child0.node, 'mouseup', { clientX: rect.left + 1, clientY: rect.top + 1 }); ['mousemove', 'keydown', 'contextmenu'].forEach(name => { let evt = generate(name); let canceled = !document.body.dispatchEvent(evt); expect(canceled).to.equal(false); }); }); }); describe('keydown', () => { it('should be prevented during a drag event', () => { let evt = generate('keydown'); let canceled = !document.body.dispatchEvent(evt); expect(canceled).to.equal(true); }); it('should dispose of the drag if `Escape` is pressed', () => { simulate(document.body, 'keydown', { keyCode: 27 }); expect(drag.isDisposed).to.equal(true); }); }); describe('mouseenter', () => { it('should be prevented during a drag event', () => { let evt = generate('mouseenter', { cancelable: true }); let canceled = !document.body.dispatchEvent(evt); expect(canceled).to.equal(true); }); }); describe('mouseleave', () => { it('should be prevented during a drag event', () => { let evt = generate('mouseleave', { cancelable: true }); let canceled = !document.body.dispatchEvent(evt); expect(canceled).to.equal(true); }); }); describe('mouseover', () => { it('should be prevented during a drag event', () => { let evt = generate('mouseover'); let canceled = !document.body.dispatchEvent(evt); expect(canceled).to.equal(true); }); }); describe('mouseout', () => { it('should be prevented during a drag event', () => { let evt = generate('mouseout'); let canceled = !document.body.dispatchEvent(evt); expect(canceled).to.equal(true); }); }); describe('keyup', () => { it('should be prevented during a drag event', () => { let evt = generate('keyup'); let canceled = !document.body.dispatchEvent(evt); expect(canceled).to.equal(true); }); }); describe('keypress', () => { it('should be prevented during a drag event', () => { let evt = generate('keypress'); let canceled = !document.body.dispatchEvent(evt); expect(canceled).to.equal(true); }); }); describe('contextmenu', () => { it('should be prevented during a drag event', () => { let evt = generate('contextmenu'); let canceled = !document.body.dispatchEvent(evt); expect(canceled).to.equal(true); }); }); }); describe('.overrideCursor()', () => { it('should update the body `cursor` style', () => { expect(document.body.style.cursor).to.equal(''); let override = Drag.overrideCursor('wait'); expect(document.body.style.cursor).to.equal('wait'); override.dispose(); }); it('should add the `lm-mod-override-cursor` class to the body', () => { expect( document.body.classList.contains('lm-mod-override-cursor') ).to.equal(false); let override = Drag.overrideCursor('wait'); expect( document.body.classList.contains('lm-mod-override-cursor') ).to.equal(true); override.dispose(); }); it('should clear the override when disposed', () => { expect(document.body.style.cursor).to.equal(''); let override = Drag.overrideCursor('wait'); expect(document.body.style.cursor).to.equal('wait'); override.dispose(); expect(document.body.style.cursor).to.equal(''); }); it('should remove the `lm-mod-override-cursor` class when disposed', () => { expect( document.body.classList.contains('lm-mod-override-cursor') ).to.equal(false); let override = Drag.overrideCursor('wait'); expect( document.body.classList.contains('lm-mod-override-cursor') ).to.equal(true); override.dispose(); expect( document.body.classList.contains('lm-mod-override-cursor') ).to.equal(false); }); it('should respect the most recent override', () => { expect(document.body.style.cursor).to.equal(''); expect( document.body.classList.contains('lm-mod-override-cursor') ).to.equal(false); let one = Drag.overrideCursor('wait'); expect(document.body.style.cursor).to.equal('wait'); expect( document.body.classList.contains('lm-mod-override-cursor') ).to.equal(true); let two = Drag.overrideCursor('default'); expect(document.body.style.cursor).to.equal('default'); expect( document.body.classList.contains('lm-mod-override-cursor') ).to.equal(true); let three = Drag.overrideCursor('cell'); expect(document.body.style.cursor).to.equal('cell'); expect( document.body.classList.contains('lm-mod-override-cursor') ).to.equal(true); two.dispose(); expect(document.body.style.cursor).to.equal('cell'); expect( document.body.classList.contains('lm-mod-override-cursor') ).to.equal(true); one.dispose(); expect(document.body.style.cursor).to.equal('cell'); expect( document.body.classList.contains('lm-mod-override-cursor') ).to.equal(true); three.dispose(); expect(document.body.style.cursor).to.equal(''); expect( document.body.classList.contains('lm-mod-override-cursor') ).to.equal(false); }); it('should override the computed cursor for a node', () => { let div = document.createElement('div'); div.style.cursor = 'cell'; document.body.appendChild(div); expect(window.getComputedStyle(div).cursor).to.equal('cell'); let override = Drag.overrideCursor('wait'); expect(window.getComputedStyle(div).cursor).to.equal('wait'); override.dispose(); expect(window.getComputedStyle(div).cursor).to.equal('cell'); document.body.removeChild(div); }); }); }); }); lumino-2021.12.13/packages/dragdrop/tests/tsconfig.json000066400000000000000000000007501415564225700226540ustar00rootroot00000000000000{ "compilerOptions": { "declaration": false, "noImplicitAny": true, "noEmitOnError": true, "noUnusedLocals": true, "strictNullChecks": true, "module": "commonjs", "moduleResolution": "node", "target": "ES5", "outDir": "build", "lib": ["ES5", "DOM"], "types": ["chai", "mocha"], "rootDir": "src" }, "include": ["src/*"], "references": [ { "path": "../../coreutils" }, { "path": "../../disposable" } ] } lumino-2021.12.13/packages/dragdrop/tests/webpack.config.js000066400000000000000000000004761415564225700233700ustar00rootroot00000000000000var path = require('path'); module.exports = { entry: './build/index.spec.js', mode: 'development', output: { filename: './build/bundle.test.js', path: path.resolve(__dirname) }, module: { rules: [ { test: /\.css$/, use: ['style-loader', 'css-loader'] } ] } }; lumino-2021.12.13/packages/dragdrop/tsconfig.json000066400000000000000000000011661415564225700215140ustar00rootroot00000000000000{ "compilerOptions": { "composite": true, "sourceMap": true, "declaration": true, "declarationDir": "./types", "declarationMap": true, "noImplicitAny": true, "noEmitOnError": true, "noUnusedLocals": true, "strictNullChecks": true, "module": "ES6", "moduleResolution": "node", "target": "ES5", "outDir": "lib", "lib": ["ES5", "ES2015.Promise", "ES2015.Iterable", "DOM"], "importHelpers": true, "types": [], "rootDir": "src" }, "include": ["src/*"], "references": [ { "path": "../coreutils" }, { "path": "../disposable" } ] } lumino-2021.12.13/packages/keyboard/000077500000000000000000000000001415564225700167775ustar00rootroot00000000000000lumino-2021.12.13/packages/keyboard/api-extractor.json000066400000000000000000000014611415564225700224560ustar00rootroot00000000000000/** * Config file for API Extractor. For more info, please visit: https://api-extractor.com */ { "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", /** * Optionally specifies another JSON config file that this file extends from. This provides a way for * standard settings to be shared across multiple projects. * * If the path starts with "./" or "../", the path is resolved relative to the folder of the file that contains * the "extends" field. Otherwise, the first path segment is interpreted as an NPM package name, and will be * resolved using NodeJS require(). * * SUPPORTED TOKENS: none * DEFAULT VALUE: "" */ "extends": "../../api-extractor-base.json" // "extends": "my-package/include/api-extractor-base.json" } lumino-2021.12.13/packages/keyboard/package.json000066400000000000000000000050001415564225700212600ustar00rootroot00000000000000{ "name": "@lumino/keyboard", "version": "1.8.1", "description": "Lumino Keyboard", "homepage": "https://github.com/jupyterlab/lumino", "bugs": { "url": "https://github.com/jupyterlab/lumino/issues" }, "repository": { "type": "git", "url": "https://github.com/jupyterlab/lumino.git" }, "license": "BSD-3-Clause", "author": "S. Chris Colbert ", "contributors": [ "A. Darian ", "Dave Willmer ", "S. Chris Colbert ", "Steven Silvester " ], "main": "dist/index.js", "jsdelivr": "dist/index.min.js", "unpkg": "dist/index.min.js", "module": "dist/index.es6", "types": "types/index.d.ts", "files": [ "dist/*", "src/*", "types/*" ], "scripts": { "api": "api-extractor run --local --verbose", "build": "npm run build:src && rollup -c", "build:src": "tsc --build", "build:test": "tsc --build tests && cd tests && webpack", "clean": "rimraf ./lib && rimraf *.tsbuildinfo && rimraf ./types && rimraf ./dist", "clean:test": "rimraf tests/build", "docs": "typedoc --options tdoptions.json src", "minimize": "terser dist/index.js -c -m --source-map \"content='dist/index.js.map',url='index.min.js.map'\" -o dist/index.min.js", "test": "npm run test:firefox-headless", "test:chrome": "cd tests && karma start --browsers=Chrome", "test:chrome-headless": "cd tests && karma start --browsers=ChromeHeadless", "test:firefox": "cd tests && karma start --browsers=Firefox", "test:firefox-headless": "cd tests && karma start --browsers=FirefoxHeadless", "test:ie": "cd tests && karma start --browsers=IE", "watch": "tsc --build --watch" }, "devDependencies": { "@microsoft/api-extractor": "^7.6.0", "@types/chai": "^3.4.35", "@types/mocha": "^2.2.39", "chai": "^4.3.4", "karma": "^6.3.4", "karma-chrome-launcher": "^3.1.0", "karma-firefox-launcher": "^2.1.1", "karma-ie-launcher": "^1.0.0", "karma-mocha": "^2.0.1", "karma-mocha-reporter": "^2.2.5", "mocha": "^9.0.3", "rimraf": "^3.0.2", "rollup": "^2.56.0", "rollup-plugin-node-resolve": "^5.2.0", "rollup-plugin-postcss": "^4.0.0", "rollup-plugin-sourcemaps": "^0.6.3", "simulate-event": "^1.4.0", "terser": "^5.7.1", "tslib": "^2.3.0", "typedoc": "~0.15.0", "typescript": "~3.6.0", "webpack": "^4.41.3", "webpack-cli": "^3.3.10" }, "publishConfig": { "access": "public" } } lumino-2021.12.13/packages/keyboard/rollup.config.js000066400000000000000000000015231415564225700221170ustar00rootroot00000000000000import nodeResolve from 'rollup-plugin-node-resolve'; import sourcemaps from 'rollup-plugin-sourcemaps'; import postcss from 'rollup-plugin-postcss'; const pkg = require('./package.json'); const globals = id => id.indexOf('@lumino/') === 0 ? id.replace('@lumino/', 'lumino_') : id; export default [ { input: 'lib/index', external: id => pkg.dependencies && !!pkg.dependencies[id], output: [ { file: pkg.main, globals, format: 'umd', sourcemap: true, name: pkg.name }, { file: pkg.module + '.js', format: 'es', sourcemap: true, name: pkg.name } ], plugins: [ nodeResolve({ preferBuiltins: true }), sourcemaps(), postcss({ extensions: ['.css'], minimize: true }) ] } ]; lumino-2021.12.13/packages/keyboard/src/000077500000000000000000000000001415564225700175665ustar00rootroot00000000000000lumino-2021.12.13/packages/keyboard/src/index.ts000066400000000000000000000204661415564225700212550ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ /** * An object which represents an abstract keyboard layout. */ export interface IKeyboardLayout { /** * The human readable name of the layout. * * This value is used primarily for display and debugging purposes. */ readonly name: string; /** * Get an array of all key values supported by the layout. * * @returns A new array of the supported key values. * * #### Notes * This can be useful for authoring tools and debugging, when it's * necessary to know which keys are available for shortcut use. */ keys(): string[]; /** * Test whether the given key is a valid value for the layout. * * @param key - The user provided key to test for validity. * * @returns `true` if the key is valid, `false` otherwise. */ isValidKey(key: string): boolean; /** * Test whether the given key is a modifier key. * * @param key - The user provided key. * * @returns `true` if the key is a modifier key, `false` otherwise. * * #### Notes * This is necessary so that we don't process modifier keys pressed * in the middle of the key sequence. * E.g. "Shift C Ctrl P" is actually 4 keydown events: * "Shift", "Shift P", "Ctrl", "Ctrl P", * and events for "Shift" and "Ctrl" should be ignored. */ isModifierKey(key: string): boolean; /** * Get the key for a `'keydown'` event. * * @param event - The event object for a `'keydown'` event. * * @returns The associated key value, or an empty string if the event * does not represent a valid primary key. */ keyForKeydownEvent(event: KeyboardEvent): string; } /** * Get the global application keyboard layout instance. * * @returns The keyboard layout for use by the application. * * #### Notes * The default keyboard layout is US-English. */ export function getKeyboardLayout(): IKeyboardLayout { return Private.keyboardLayout; } /** * Set the global application keyboard layout instance. * * @param - The keyboard layout for use by the application. * * #### Notes * The keyboard layout should typically be set on application startup * to a layout which is appropriate for the user's system. */ export function setKeyboardLayout(layout: IKeyboardLayout): void { Private.keyboardLayout = layout; } /** * A concrete implementation of [[IKeyboardLayout]] based on keycodes. * * The `keyCode` property of a `'keydown'` event is a browser and OS * specific representation of the physical key (not character) which * was pressed on a keyboard. While not the most convenient API, it * is currently the only one which works reliably on all browsers. * * This class accepts a user-defined mapping of keycode to key, which * allows for reliable shortcuts tailored to the user's system. */ export class KeycodeLayout implements IKeyboardLayout { /** * Construct a new keycode layout. * * @param name - The human readable name for the layout. * * @param codes - A mapping of keycode to key value. * * @param modifierKeys - Array of modifier key names */ constructor( name: string, codes: KeycodeLayout.CodeMap, modifierKeys: string[] = [] ) { this.name = name; this._codes = codes; this._keys = KeycodeLayout.extractKeys(codes); this._modifierKeys = KeycodeLayout.convertToKeySet(modifierKeys); } /** * The human readable name of the layout. */ readonly name: string; /** * Get an array of the key values supported by the layout. * * @returns A new array of the supported key values. */ keys(): string[] { return Object.keys(this._keys); } /** * Test whether the given key is a valid value for the layout. * * @param key - The user provided key to test for validity. * * @returns `true` if the key is valid, `false` otherwise. */ isValidKey(key: string): boolean { return key in this._keys; } /** * Test whether the given key is a modifier key. * * @param key - The user provided key. * * @returns `true` if the key is a modifier key, `false` otherwise. */ isModifierKey(key: string): boolean { return key in this._modifierKeys; } /** * Get the key for a `'keydown'` event. * * @param event - The event object for a `'keydown'` event. * * @returns The associated key value, or an empty string if * the event does not represent a valid primary key. */ keyForKeydownEvent(event: KeyboardEvent): string { return this._codes[event.keyCode] || ''; } private _keys: KeycodeLayout.KeySet; private _codes: KeycodeLayout.CodeMap; private _modifierKeys: KeycodeLayout.KeySet; } /** * The namespace for the `KeycodeLayout` class statics. */ export namespace KeycodeLayout { /** * A type alias for a keycode map. */ export type CodeMap = { readonly [code: number]: string }; /** * A type alias for a key set. */ export type KeySet = { readonly [key: string]: boolean }; /** * Extract the set of keys from a code map. * * @param code - The code map of interest. * * @returns A set of the keys in the code map. */ export function extractKeys(codes: CodeMap): KeySet { let keys: any = Object.create(null); for (let c in codes) { keys[codes[c]] = true; } return keys as KeySet; } /** * Convert array of keys to a key set. * * @param keys - The array that needs to be converted * * @returns A set of the keys in the array. */ export function convertToKeySet(keys: string[]): KeySet { let keySet = Object(null); for (let i = 0, n = keys.length; i < n; ++i) { keySet[keys[i]] = true; } return keySet; } } /** * A keycode-based keyboard layout for US English keyboards. * * This layout is valid for the following OS/Browser combinations. * * - Windows * - Chrome * - Firefox * - IE * * - OSX * - Chrome * - Firefox * - Safari * * - Linux * - Chrome * - Firefox * * Other combinations may also work, but are untested. */ export const EN_US: IKeyboardLayout = new KeycodeLayout( 'en-us', { 8: 'Backspace', 9: 'Tab', 13: 'Enter', 16: 'Shift', 17: 'Ctrl', 18: 'Alt', 19: 'Pause', 27: 'Escape', 32: 'Space', 33: 'PageUp', 34: 'PageDown', 35: 'End', 36: 'Home', 37: 'ArrowLeft', 38: 'ArrowUp', 39: 'ArrowRight', 40: 'ArrowDown', 45: 'Insert', 46: 'Delete', 48: '0', 49: '1', 50: '2', 51: '3', 52: '4', 53: '5', 54: '6', 55: '7', 56: '8', 57: '9', 59: ';', // firefox 61: '=', // firefox 65: 'A', 66: 'B', 67: 'C', 68: 'D', 69: 'E', 70: 'F', 71: 'G', 72: 'H', 73: 'I', 74: 'J', 75: 'K', 76: 'L', 77: 'M', 78: 'N', 79: 'O', 80: 'P', 81: 'Q', 82: 'R', 83: 'S', 84: 'T', 85: 'U', 86: 'V', 87: 'W', 88: 'X', 89: 'Y', 90: 'Z', 91: 'Meta', // non-firefox 93: 'ContextMenu', 96: '0', // numpad 97: '1', // numpad 98: '2', // numpad 99: '3', // numpad 100: '4', // numpad 101: '5', // numpad 102: '6', // numpad 103: '7', // numpad 104: '8', // numpad 105: '9', // numpad 106: '*', // numpad 107: '+', // numpad 109: '-', // numpad 110: '.', // numpad 111: '/', // numpad 112: 'F1', 113: 'F2', 114: 'F3', 115: 'F4', 116: 'F5', 117: 'F6', 118: 'F7', 119: 'F8', 120: 'F9', 121: 'F10', 122: 'F11', 123: 'F12', 173: '-', // firefox 186: ';', // non-firefox 187: '=', // non-firefox 188: ',', 189: '-', // non-firefox 190: '.', 191: '/', 192: '`', 219: '[', 220: '\\', 221: ']', 222: "'", 224: 'Meta' // firefox }, ['Shift', 'Ctrl', 'Alt', 'Meta'] // modifier keys ); /** * The namespace for the module implementation details. */ namespace Private { /** * The global keyboard layout instance. */ export let keyboardLayout = EN_US; } lumino-2021.12.13/packages/keyboard/tdoptions.json000066400000000000000000000005171415564225700217200ustar00rootroot00000000000000{ "excludeNotExported": true, "mode": "file", "target": "es5", "module": "es5", "lib": [ "lib.es2015.d.ts", "lib.es2015.collection.d.ts", "lib.es2015.promise.d.ts", "lib.dom.d.ts" ], "out": "../../docs/source/api/keyboard", "baseUrl": ".", "paths": { "@lumino/*": ["node_modules/@lumino/*"] } } lumino-2021.12.13/packages/keyboard/tests/000077500000000000000000000000001415564225700201415ustar00rootroot00000000000000lumino-2021.12.13/packages/keyboard/tests/karma.conf.js000066400000000000000000000005051415564225700225160ustar00rootroot00000000000000module.exports = function (config) { config.set({ basePath: '.', frameworks: ['mocha'], reporters: ['mocha'], files: ['build/bundle.test.js'], port: 9876, colors: true, singleRun: true, browserNoActivityTimeout: 30000, failOnEmptyTestSuite: false, logLevel: config.LOG_INFO }); }; lumino-2021.12.13/packages/keyboard/tests/src/000077500000000000000000000000001415564225700207305ustar00rootroot00000000000000lumino-2021.12.13/packages/keyboard/tests/src/index.spec.ts000066400000000000000000000122651415564225700233460ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { expect } from 'chai'; import { generate } from 'simulate-event'; import { EN_US, getKeyboardLayout, KeycodeLayout, setKeyboardLayout } from '@lumino/keyboard'; describe('@lumino/keyboard', () => { describe('getKeyboardLayout()', () => { it('should return the global keyboard layout', () => { expect(getKeyboardLayout()).to.equal(EN_US); }); }); describe('setKeyboardLayout()', () => { it('should set the global keyboard layout', () => { let layout = new KeycodeLayout('ab-cd', {}); setKeyboardLayout(layout); expect(getKeyboardLayout()).to.equal(layout); setKeyboardLayout(EN_US); expect(getKeyboardLayout()).to.equal(EN_US); }); }); describe('KeycodeLayout', () => { describe('#constructor()', () => { it('should construct a new keycode layout', () => { let layout = new KeycodeLayout('ab-cd', {}); expect(layout).to.be.an.instanceof(KeycodeLayout); }); }); describe('#name', () => { it('should be a human readable name of the layout', () => { let layout = new KeycodeLayout('ab-cd', {}); expect(layout.name).to.equal('ab-cd'); }); }); describe('#keys()', () => { it('should get an array of all key values supported by the layout', () => { let layout = new KeycodeLayout('ab-cd', { 100: 'F' }); let keys = layout.keys(); expect(keys.length).to.equal(1); expect(keys[0]).to.equal('F'); }); }); describe('#isValidKey()', () => { it('should test whether the key is valid for the layout', () => { let layout = new KeycodeLayout('foo', { 100: 'F' }); expect(layout.isValidKey('F')).to.equal(true); expect(layout.isValidKey('A')).to.equal(false); }); it('should treat modifier keys as valid', () => { let layout = new KeycodeLayout('foo', { 100: 'F', 101: 'A' }, ['A']); expect(layout.isValidKey('A')).to.equal(true); }); }); describe('#isModifierKey()', () => { it('should test whether the key is modifier for the layout', () => { let layout = new KeycodeLayout('foo', { 100: 'F', 101: 'A' }, ['A']); expect(layout.isModifierKey('F')).to.equal(false); expect(layout.isModifierKey('A')).to.equal(true); }); it('should return false for keys that are not in the layout', () => { let layout = new KeycodeLayout('foo', { 100: 'F', 101: 'A' }, ['A']); expect(layout.isModifierKey('B')).to.equal(false); }); }); describe('#keyForKeydownEvent()', () => { it('should get the key for a `keydown` event', () => { let layout = new KeycodeLayout('foo', { 100: 'F' }); let event = generate('keydown', { keyCode: 100 }); let key = layout.keyForKeydownEvent(event as KeyboardEvent); expect(key).to.equal('F'); }); it('should return an empty string if the code is not valid', () => { let layout = new KeycodeLayout('foo', { 100: 'F' }); let event = generate('keydown', { keyCode: 101 }); let key = layout.keyForKeydownEvent(event as KeyboardEvent); expect(key).to.equal(''); }); }); describe('.extractKeys()', () => { it('should extract the keys from a code map', () => { let keys: KeycodeLayout.CodeMap = { 70: 'F', 71: 'G', 72: 'H' }; let goal: KeycodeLayout.KeySet = { F: true, G: true, H: true }; expect(KeycodeLayout.extractKeys(keys)).to.deep.equal(goal); }); }); describe('.convertToKeySet()', () => { it('should convert key array to key set', () => { let keys: string[] = ['F', 'G', 'H']; let goal: KeycodeLayout.KeySet = { F: true, G: true, H: true }; expect(KeycodeLayout.convertToKeySet(keys)).to.deep.equal(goal); }); }); }); describe('EN_US', () => { it('should be a keycode layout', () => { expect(EN_US).to.be.an.instanceof(KeycodeLayout); }); it('should have standardized keys', () => { expect(EN_US.isValidKey('A')).to.equal(true); expect(EN_US.isValidKey('Z')).to.equal(true); expect(EN_US.isValidKey('0')).to.equal(true); expect(EN_US.isValidKey('a')).to.equal(false); }); it('should have modifier keys', () => { expect(EN_US.isValidKey('Shift')).to.equal(true); expect(EN_US.isValidKey('Ctrl')).to.equal(true); expect(EN_US.isValidKey('Alt')).to.equal(true); expect(EN_US.isValidKey('Meta')).to.equal(true); }); it('should correctly detect modifier keys', () => { expect(EN_US.isModifierKey('Shift')).to.equal(true); expect(EN_US.isModifierKey('Ctrl')).to.equal(true); expect(EN_US.isModifierKey('Alt')).to.equal(true); expect(EN_US.isModifierKey('Meta')).to.equal(true); }); }); }); lumino-2021.12.13/packages/keyboard/tests/tsconfig.json000066400000000000000000000005671415564225700226600ustar00rootroot00000000000000{ "compilerOptions": { "declaration": false, "noImplicitAny": true, "noEmitOnError": true, "noUnusedLocals": true, "strictNullChecks": true, "module": "commonjs", "moduleResolution": "node", "target": "ES5", "outDir": "build", "lib": ["ES5", "DOM"], "types": ["chai", "mocha"], "rootDir": "src" }, "include": ["src/*"] } lumino-2021.12.13/packages/keyboard/tests/webpack.config.js000066400000000000000000000003061415564225700233560ustar00rootroot00000000000000var path = require('path'); module.exports = { entry: './build/index.spec.js', mode: 'development', output: { filename: './build/bundle.test.js', path: path.resolve(__dirname) } }; lumino-2021.12.13/packages/keyboard/tsconfig.json000066400000000000000000000007711415564225700215130ustar00rootroot00000000000000{ "compilerOptions": { "composite": true, "sourceMap": true, "declaration": true, "declarationDir": "./types", "declarationMap": true, "noImplicitAny": true, "noEmitOnError": true, "noUnusedLocals": true, "strictNullChecks": true, "module": "ES6", "moduleResolution": "node", "target": "ES5", "outDir": "lib", "lib": ["ES5", "DOM", "ES2015.Iterable"], "importHelpers": true, "types": [], "rootDir": "src" }, "include": ["src/*"] } lumino-2021.12.13/packages/messaging/000077500000000000000000000000001415564225700171545ustar00rootroot00000000000000lumino-2021.12.13/packages/messaging/api-extractor.json000066400000000000000000000014611415564225700226330ustar00rootroot00000000000000/** * Config file for API Extractor. For more info, please visit: https://api-extractor.com */ { "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", /** * Optionally specifies another JSON config file that this file extends from. This provides a way for * standard settings to be shared across multiple projects. * * If the path starts with "./" or "../", the path is resolved relative to the folder of the file that contains * the "extends" field. Otherwise, the first path segment is interpreted as an NPM package name, and will be * resolved using NodeJS require(). * * SUPPORTED TOKENS: none * DEFAULT VALUE: "" */ "extends": "../../api-extractor-base.json" // "extends": "my-package/include/api-extractor-base.json" } lumino-2021.12.13/packages/messaging/package.json000066400000000000000000000051511415564225700214440ustar00rootroot00000000000000{ "name": "@lumino/messaging", "version": "1.10.1", "description": "Lumino Message Passing", "homepage": "https://github.com/jupyterlab/lumino", "bugs": { "url": "https://github.com/jupyterlab/lumino/issues" }, "repository": { "type": "git", "url": "https://github.com/jupyterlab/lumino.git" }, "license": "BSD-3-Clause", "author": "S. Chris Colbert ", "contributors": [ "A. Darian ", "Dave Willmer ", "S. Chris Colbert ", "Steven Silvester " ], "main": "dist/index.js", "jsdelivr": "dist/index.min.js", "unpkg": "dist/index.min.js", "module": "dist/index.es6", "types": "types/index.d.ts", "files": [ "dist/*", "src/*", "types/*" ], "scripts": { "api": "api-extractor run --local --verbose", "build": "npm run build:src && rollup -c", "build:src": "tsc --build", "build:test": "tsc --build tests && cd tests && webpack", "clean": "rimraf ./lib && rimraf *.tsbuildinfo && rimraf ./types && rimraf ./dist", "clean:test": "rimraf tests/build", "docs": "typedoc --options tdoptions.json src", "minimize": "terser dist/index.js -c -m --source-map \"content='dist/index.js.map',url='index.min.js.map'\" -o dist/index.min.js", "test": "npm run test:firefox-headless", "test:chrome": "cd tests && karma start --browsers=Chrome", "test:chrome-headless": "cd tests && karma start --browsers=ChromeHeadless", "test:firefox": "cd tests && karma start --browsers=Firefox", "test:firefox-headless": "cd tests && karma start --browsers=FirefoxHeadless", "test:ie": "cd tests && karma start --browsers=IE", "watch": "tsc --build --watch" }, "dependencies": { "@lumino/algorithm": "^1.9.1", "@lumino/collections": "^1.9.1" }, "devDependencies": { "@microsoft/api-extractor": "^7.6.0", "@types/chai": "^3.4.35", "@types/mocha": "^2.2.39", "@types/node": "^12.12.17", "chai": "^4.3.4", "karma": "^6.3.4", "karma-chrome-launcher": "^3.1.0", "karma-firefox-launcher": "^2.1.1", "karma-ie-launcher": "^1.0.0", "karma-mocha": "^2.0.1", "karma-mocha-reporter": "^2.2.5", "mocha": "^9.0.3", "rimraf": "^3.0.2", "rollup": "^2.56.0", "rollup-plugin-node-resolve": "^5.2.0", "rollup-plugin-postcss": "^4.0.0", "rollup-plugin-sourcemaps": "^0.6.3", "terser": "^5.7.1", "tslib": "^2.3.0", "typedoc": "~0.15.0", "typescript": "~3.6.0", "webpack": "^4.41.3", "webpack-cli": "^3.3.10" }, "publishConfig": { "access": "public" } } lumino-2021.12.13/packages/messaging/rollup.config.js000066400000000000000000000015231415564225700222740ustar00rootroot00000000000000import nodeResolve from 'rollup-plugin-node-resolve'; import sourcemaps from 'rollup-plugin-sourcemaps'; import postcss from 'rollup-plugin-postcss'; const pkg = require('./package.json'); const globals = id => id.indexOf('@lumino/') === 0 ? id.replace('@lumino/', 'lumino_') : id; export default [ { input: 'lib/index', external: id => pkg.dependencies && !!pkg.dependencies[id], output: [ { file: pkg.main, globals, format: 'umd', sourcemap: true, name: pkg.name }, { file: pkg.module + '.js', format: 'es', sourcemap: true, name: pkg.name } ], plugins: [ nodeResolve({ preferBuiltins: true }), sourcemaps(), postcss({ extensions: ['.css'], minimize: true }) ] } ]; lumino-2021.12.13/packages/messaging/src/000077500000000000000000000000001415564225700177435ustar00rootroot00000000000000lumino-2021.12.13/packages/messaging/src/index.ts000066400000000000000000000434221415564225700214270ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { ArrayExt, each, every, retro, some } from '@lumino/algorithm'; import { LinkedList } from '@lumino/collections'; /** * A message which can be delivered to a message handler. * * #### Notes * This class may be subclassed to create complex message types. */ export class Message { /** * Construct a new message. * * @param type - The type of the message. */ constructor(type: string) { this.type = type; } /** * The type of the message. * * #### Notes * The `type` of a message should be related directly to its actual * runtime type. This means that `type` can and will be used to cast * the message to the relevant derived `Message` subtype. */ readonly type: string; /** * Test whether the message is conflatable. * * #### Notes * Message conflation is an advanced topic. Most message types will * not make use of this feature. * * If a conflatable message is posted to a handler while another * conflatable message of the same `type` has already been posted * to the handler, the `conflate()` method of the existing message * will be invoked. If that method returns `true`, the new message * will not be enqueued. This allows messages to be compressed, so * that only a single instance of the message type is processed per * cycle, no matter how many times messages of that type are posted. * * Custom message types may reimplement this property. * * The default implementation is always `false`. */ get isConflatable(): boolean { return false; } /** * Conflate this message with another message of the same `type`. * * @param other - A conflatable message of the same `type`. * * @returns `true` if the message was successfully conflated, or * `false` otherwise. * * #### Notes * Message conflation is an advanced topic. Most message types will * not make use of this feature. * * This method is called automatically by the message loop when the * given message is posted to the handler paired with this message. * This message will already be enqueued and conflatable, and the * given message will have the same `type` and also be conflatable. * * This method should merge the state of the other message into this * message as needed so that when this message is finally delivered * to the handler, it receives the most up-to-date information. * * If this method returns `true`, it signals that the other message * was successfully conflated and that message will not be enqueued. * * If this method returns `false`, the other message will be enqueued * for normal delivery. * * Custom message types may reimplement this method. * * The default implementation always returns `false`. */ conflate(other: Message): boolean { return false; } } /** * A convenience message class which conflates automatically. * * #### Notes * Message conflation is an advanced topic. Most user code will not * make use of this class. * * This message class is useful for creating message instances which * should be conflated, but which have no state other than `type`. * * If conflation of stateful messages is required, a custom `Message` * subclass should be created. */ export class ConflatableMessage extends Message { /** * Test whether the message is conflatable. * * #### Notes * This property is always `true`. */ get isConflatable(): boolean { return true; } /** * Conflate this message with another message of the same `type`. * * #### Notes * This method always returns `true`. */ conflate(other: ConflatableMessage): boolean { return true; } } /** * An object which handles messages. * * #### Notes * A message handler is a simple way of defining a type which can act * upon on a large variety of external input without requiring a large * abstract API surface. This is particularly useful in the context of * widget frameworks where the number of distinct message types can be * unbounded. */ export interface IMessageHandler { /** * Process a message sent to the handler. * * @param msg - The message to be processed. */ processMessage(msg: Message): void; } /** * An object which intercepts messages sent to a message handler. * * #### Notes * A message hook is useful for intercepting or spying on messages * sent to message handlers which were either not created by the * consumer, or when subclassing the handler is not feasible. * * If `messageHook` returns `false`, no other message hooks will be * invoked and the message will not be delivered to the handler. * * If all installed message hooks return `true`, the message will * be delivered to the handler for processing. * * **See also:** [[installMessageHook]] and [[removeMessageHook]] */ export interface IMessageHook { /** * Intercept a message sent to a message handler. * * @param handler - The target handler of the message. * * @param msg - The message to be sent to the handler. * * @returns `true` if the message should continue to be processed * as normal, or `false` if processing should cease immediately. */ messageHook(handler: IMessageHandler, msg: Message): boolean; } /** * A type alias for message hook object or function. * * #### Notes * The signature and semantics of a message hook function are the same * as the `messageHook` method of [[IMessageHook]]. */ export type MessageHook = | IMessageHook | ((handler: IMessageHandler, msg: Message) => boolean); /** * The namespace for the global singleton message loop. */ export namespace MessageLoop { /** * Send a message to a message handler to process immediately. * * @param handler - The handler which should process the message. * * @param msg - The message to deliver to the handler. * * #### Notes * The message will first be sent through any installed message hooks * for the handler. If the message passes all hooks, it will then be * delivered to the `processMessage` method of the handler. * * The message will not be conflated with pending posted messages. * * Exceptions in hooks and handlers will be caught and logged. */ export function sendMessage(handler: IMessageHandler, msg: Message): void { // Lookup the message hooks for the handler. let hooks = messageHooks.get(handler); // Handle the common case of no installed hooks. if (!hooks || hooks.length === 0) { invokeHandler(handler, msg); return; } // Invoke the message hooks starting with the newest first. let passed = every(retro(hooks), hook => { return hook ? invokeHook(hook, handler, msg) : true; }); // Invoke the handler if the message passes all hooks. if (passed) { invokeHandler(handler, msg); } } /** * Post a message to a message handler to process in the future. * * @param handler - The handler which should process the message. * * @param msg - The message to post to the handler. * * #### Notes * The message will be conflated with the pending posted messages for * the handler, if possible. If the message is not conflated, it will * be queued for normal delivery on the next cycle of the event loop. * * Exceptions in hooks and handlers will be caught and logged. */ export function postMessage(handler: IMessageHandler, msg: Message): void { // Handle the common case of a non-conflatable message. if (!msg.isConflatable) { enqueueMessage(handler, msg); return; } // Conflate the message with an existing message if possible. let conflated = some(messageQueue, posted => { if (posted.handler !== handler) { return false; } if (!posted.msg) { return false; } if (posted.msg.type !== msg.type) { return false; } if (!posted.msg.isConflatable) { return false; } return posted.msg.conflate(msg); }); // Enqueue the message if it was not conflated. if (!conflated) { enqueueMessage(handler, msg); } } /** * Install a message hook for a message handler. * * @param handler - The message handler of interest. * * @param hook - The message hook to install. * * #### Notes * A message hook is invoked before a message is delivered to the * handler. If the hook returns `false`, no other hooks will be * invoked and the message will not be delivered to the handler. * * The most recently installed message hook is executed first. * * If the hook is already installed, this is a no-op. */ export function installMessageHook( handler: IMessageHandler, hook: MessageHook ): void { // Lookup the hooks for the handler. let hooks = messageHooks.get(handler); // Bail early if the hook is already installed. if (hooks && hooks.indexOf(hook) !== -1) { return; } // Add the hook to the end, so it will be the first to execute. if (!hooks) { messageHooks.set(handler, [hook]); } else { hooks.push(hook); } } /** * Remove an installed message hook for a message handler. * * @param handler - The message handler of interest. * * @param hook - The message hook to remove. * * #### Notes * It is safe to call this function while the hook is executing. * * If the hook is not installed, this is a no-op. */ export function removeMessageHook( handler: IMessageHandler, hook: MessageHook ): void { // Lookup the hooks for the handler. let hooks = messageHooks.get(handler); // Bail early if the hooks do not exist. if (!hooks) { return; } // Lookup the index of the hook and bail if not found. let i = hooks.indexOf(hook); if (i === -1) { return; } // Clear the hook and schedule a cleanup of the array. hooks[i] = null; scheduleCleanup(hooks); } /** * Clear all message data associated with a message handler. * * @param handler - The message handler of interest. * * #### Notes * This will clear all posted messages and hooks for the handler. */ export function clearData(handler: IMessageHandler): void { // Lookup the hooks for the handler. let hooks = messageHooks.get(handler); // Clear all messsage hooks for the handler. if (hooks && hooks.length > 0) { ArrayExt.fill(hooks, null); scheduleCleanup(hooks); } // Clear all posted messages for the handler. each(messageQueue, posted => { if (posted.handler === handler) { posted.handler = null; posted.msg = null; } }); } /** * Process the pending posted messages in the queue immediately. * * #### Notes * This function is useful when posted messages must be processed * immediately, instead of on the next animation frame. * * This function should normally not be needed, but it may be * required to work around certain browser idiosyncrasies. * * Recursing into this function is a no-op. */ export function flush(): void { // Bail if recursion is detected or if there is no pending task. if (flushGuard || loopTaskID === 0) { return; } // Unschedule the pending loop task. unschedule(loopTaskID); // Run the message loop within the recursion guard. flushGuard = true; runMessageLoop(); flushGuard = false; } /** * A type alias for the exception handler function. */ export type ExceptionHandler = (err: Error) => void; /** * Get the message loop exception handler. * * @returns The current exception handler. * * #### Notes * The default exception handler is `console.error`. */ export function getExceptionHandler(): ExceptionHandler { return exceptionHandler; } /** * Set the message loop exception handler. * * @param handler - The function to use as the exception handler. * * @returns The old exception handler. * * #### Notes * The exception handler is invoked when a message handler or a * message hook throws an exception. */ export function setExceptionHandler( handler: ExceptionHandler ): ExceptionHandler { let old = exceptionHandler; exceptionHandler = handler; return old; } /** * A type alias for a posted message pair. */ type PostedMessage = { handler: IMessageHandler | null; msg: Message | null }; /** * The queue of posted message pairs. */ const messageQueue = new LinkedList(); /** * A mapping of handler to array of installed message hooks. */ const messageHooks = new WeakMap< IMessageHandler, Array >(); /** * A set of message hook arrays which are pending cleanup. */ const dirtySet = new Set>(); /** * The message loop exception handler. */ let exceptionHandler: ExceptionHandler = (err: Error) => { console.error(err); }; type ScheduleHandle = number | any; // requestAnimationFrame (number) and setImmediate (any) /** * The id of the pending loop task animation frame. */ let loopTaskID: ScheduleHandle = 0; /** * A guard flag to prevent flush recursion. */ let flushGuard = false; /** * A function to schedule an event loop callback. */ const schedule = ((): ScheduleHandle => { let ok = typeof requestAnimationFrame === 'function'; return ok ? requestAnimationFrame : setImmediate; })(); /** * A function to unschedule an event loop callback. */ const unschedule = (() => { let ok = typeof cancelAnimationFrame === 'function'; return ok ? cancelAnimationFrame : clearImmediate; })(); /** * Invoke a message hook with the specified handler and message. * * Returns the result of the hook, or `true` if the hook throws. * * Exceptions in the hook will be caught and logged. */ function invokeHook( hook: MessageHook, handler: IMessageHandler, msg: Message ): boolean { let result = true; try { if (typeof hook === 'function') { result = hook(handler, msg); } else { result = hook.messageHook(handler, msg); } } catch (err) { exceptionHandler(err); } return result; } /** * Invoke a message handler with the specified message. * * Exceptions in the handler will be caught and logged. */ function invokeHandler(handler: IMessageHandler, msg: Message): void { try { handler.processMessage(msg); } catch (err) { exceptionHandler(err); } } /** * Add a message to the end of the message queue. * * This will automatically schedule a run of the message loop. */ function enqueueMessage(handler: IMessageHandler, msg: Message): void { // Add the posted message to the queue. messageQueue.addLast({ handler, msg }); // Bail if a loop task is already pending. if (loopTaskID !== 0) { return; } // Schedule a run of the message loop. loopTaskID = schedule(runMessageLoop); } /** * Run an iteration of the message loop. * * This will process all pending messages in the queue. If a message * is added to the queue while the message loop is running, it will * be processed on the next cycle of the loop. */ function runMessageLoop(): void { // Clear the task ID so the next loop can be scheduled. loopTaskID = 0; // If the message queue is empty, there is nothing else to do. if (messageQueue.isEmpty) { return; } // Add a sentinel value to the end of the queue. The queue will // only be processed up to the sentinel. Messages posted during // this cycle will execute on the next cycle. let sentinel: PostedMessage = { handler: null, msg: null }; messageQueue.addLast(sentinel); // Enter the message loop. // eslint-disable-next-line no-constant-condition while (true) { // Remove the first posted message in the queue. let posted = messageQueue.removeFirst()!; // If the value is the sentinel, exit the loop. if (posted === sentinel) { return; } // Dispatch the message if it has not been cleared. if (posted.handler && posted.msg) { sendMessage(posted.handler, posted.msg); } } } /** * Schedule a cleanup of a message hooks array. * * This will add the array to the dirty set and schedule a deferred * cleanup of the array contents. On cleanup, any `null` hook will * be removed from the array. */ function scheduleCleanup(hooks: Array): void { if (dirtySet.size === 0) { schedule(cleanupDirtySet); } dirtySet.add(hooks); } /** * Cleanup the message hook arrays in the dirty set. * * This function should only be invoked asynchronously, when the * stack frame is guaranteed to not be on the path of user code. */ function cleanupDirtySet(): void { dirtySet.forEach(cleanupHooks); dirtySet.clear(); } /** * Cleanup the dirty hooks in a message hooks array. * * This will remove any `null` hook from the array. * * This function should only be invoked asynchronously, when the * stack frame is guaranteed to not be on the path of user code. */ function cleanupHooks(hooks: Array): void { ArrayExt.removeAllWhere(hooks, isNull); } /** * Test whether a value is `null`. */ function isNull(value: T | null): boolean { return value === null; } } lumino-2021.12.13/packages/messaging/tdoptions.json000066400000000000000000000005201415564225700220670ustar00rootroot00000000000000{ "excludeNotExported": true, "mode": "file", "target": "es5", "module": "es5", "lib": [ "lib.es2015.d.ts", "lib.es2015.collection.d.ts", "lib.es2015.promise.d.ts", "lib.dom.d.ts" ], "out": "../../docs/source/api/messaging", "baseUrl": ".", "paths": { "@lumino/*": ["node_modules/@lumino/*"] } } lumino-2021.12.13/packages/messaging/tests/000077500000000000000000000000001415564225700203165ustar00rootroot00000000000000lumino-2021.12.13/packages/messaging/tests/karma.conf.js000066400000000000000000000005051415564225700226730ustar00rootroot00000000000000module.exports = function (config) { config.set({ basePath: '.', frameworks: ['mocha'], reporters: ['mocha'], files: ['build/bundle.test.js'], port: 9876, colors: true, singleRun: true, browserNoActivityTimeout: 30000, failOnEmptyTestSuite: false, logLevel: config.LOG_INFO }); }; lumino-2021.12.13/packages/messaging/tests/src/000077500000000000000000000000001415564225700211055ustar00rootroot00000000000000lumino-2021.12.13/packages/messaging/tests/src/index.spec.ts000066400000000000000000000533511415564225700235240ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { expect } from 'chai'; import { ConflatableMessage, IMessageHandler, IMessageHook, Message, MessageHook, MessageLoop } from '@lumino/messaging'; class Handler implements IMessageHandler { messages: string[] = []; processMessage(msg: Message): void { this.messages.push(msg.type); } } class BadHandler implements IMessageHandler { processMessage(msg: Message): void { throw new Error('process error'); } } class GlobalHandler extends Handler { static messages: string[] = []; processMessage(msg: Message): void { super.processMessage(msg); GlobalHandler.messages.push(msg.type); } } class LogHook implements IMessageHook { preventTypes: string[] = []; messages: string[] = []; handlers: IMessageHandler[] = []; messageHook(handler: IMessageHandler, msg: Message): boolean { this.messages.push(msg.type); this.handlers.push(handler); return this.preventTypes.indexOf(msg.type) === -1; } } const defer = (() => { let ok = typeof requestAnimationFrame === 'function'; return ok ? requestAnimationFrame : setImmediate; })(); describe('@lumino/messaging', () => { describe('Message', () => { describe('#constructor()', () => { it('should require a single message type argument', () => { let msg = new Message('test'); expect(msg).to.be.an.instanceof(Message); }); }); describe('#type', () => { it('should return the message type', () => { let msg = new Message('test'); expect(msg.type).to.equal('test'); }); }); describe('#isConflatable', () => { it('should be `false` by default', () => { let msg = new Message('test'); expect(msg.isConflatable).to.equal(false); }); }); describe('#conflate()', () => { it('should return `false` by default', () => { let msg = new Message('test'); let other = new Message('test'); expect(msg.conflate(other)).to.equal(false); }); }); }); describe('ConflatableMessage', () => { describe('#constructor()', () => { it('should require a single message type argument', () => { let msg = new ConflatableMessage('test'); expect(msg).to.be.an.instanceof(ConflatableMessage); }); it('should extend the base `Message` class', () => { let msg = new ConflatableMessage('test'); expect(msg).to.be.an.instanceof(Message); }); }); describe('#isConflatable', () => { it('should be `true` by default', () => { let msg = new ConflatableMessage('test'); expect(msg.isConflatable).to.equal(true); }); }); describe('#conflate()', () => { it('should return `true` by default', () => { let msg = new ConflatableMessage('test'); let other = new ConflatableMessage('test'); expect(msg.conflate(other)).to.equal(true); }); }); }); describe('IMessageHandler', () => { describe('#processMessage()', () => { it('should process the messages sent to the handler', () => { let handler = new Handler(); MessageLoop.sendMessage(handler, new Message('one')); MessageLoop.sendMessage(handler, new Message('two')); MessageLoop.sendMessage(handler, new Message('three')); expect(handler.messages).to.deep.equal(['one', 'two', 'three']); }); }); }); describe('IMessageHook', () => { describe('#messageHook()', () => { it('should be called for every message sent to a handler', () => { let handler = new Handler(); let logHook = new LogHook(); MessageLoop.installMessageHook(handler, logHook); MessageLoop.sendMessage(handler, new Message('one')); MessageLoop.sendMessage(handler, new Message('two')); MessageLoop.sendMessage(handler, new Message('three')); expect(handler.messages).to.deep.equal(['one', 'two', 'three']); expect(logHook.messages).to.deep.equal(['one', 'two', 'three']); expect(logHook.handlers.length).to.equal(3); for (let i of [0, 1, 2]) { expect(logHook.handlers[i]).to.equal(handler); } }); it('should block messages which do not pass the hook', () => { let handler1 = new Handler(); let handler2 = new Handler(); let logHook = new LogHook(); logHook.preventTypes = ['one', 'two']; MessageLoop.installMessageHook(handler1, logHook); MessageLoop.installMessageHook(handler2, logHook); MessageLoop.sendMessage(handler1, new Message('one')); MessageLoop.sendMessage(handler2, new Message('one')); MessageLoop.sendMessage(handler1, new Message('two')); MessageLoop.sendMessage(handler2, new Message('two')); MessageLoop.sendMessage(handler1, new Message('three')); MessageLoop.sendMessage(handler2, new Message('three')); expect(handler1.messages).to.deep.equal(['three']); expect(handler2.messages).to.deep.equal(['three']); expect(logHook.messages).to.deep.equal([ 'one', 'one', 'two', 'two', 'three', 'three' ]); expect(logHook.handlers.length).to.equal(6); for (let i of [0, 2, 4]) { expect(logHook.handlers[i]).to.equal(handler1); expect(logHook.handlers[i + 1]).to.equal(handler2); } }); }); }); describe('MessageLoop', () => { describe('sendMessage()', () => { it('should send a message to the handler to process immediately', () => { let handler = new Handler(); expect(handler.messages).to.deep.equal([]); MessageLoop.sendMessage(handler, new Message('one')); expect(handler.messages).to.deep.equal(['one']); MessageLoop.sendMessage(handler, new Message('two')); expect(handler.messages).to.deep.equal(['one', 'two']); }); it('should not conflate the message', () => { let handler = new Handler(); let msg = new ConflatableMessage('one'); MessageLoop.sendMessage(handler, msg); MessageLoop.sendMessage(handler, msg); MessageLoop.sendMessage(handler, msg); expect(handler.messages).to.deep.equal(['one', 'one', 'one']); }); it('should first run the message through the message hooks', () => { let handler = new Handler(); let logHook1 = new LogHook(); let logHook2 = new LogHook(); logHook1.preventTypes = ['one']; logHook2.preventTypes = ['two']; MessageLoop.installMessageHook(handler, logHook1); MessageLoop.installMessageHook(handler, logHook2); MessageLoop.sendMessage(handler, new Message('one')); MessageLoop.sendMessage(handler, new Message('two')); MessageLoop.sendMessage(handler, new Message('three')); expect(handler.messages).to.deep.equal(['three']); expect(logHook1.messages).to.deep.equal(['one', 'three']); expect(logHook2.messages).to.deep.equal(['one', 'two', 'three']); }); it('should stop dispatching on the first `false` hook result', () => { let handler = new Handler(); let logHook1 = new LogHook(); let logHook2 = new LogHook(); let logHook3 = new LogHook(); logHook1.preventTypes = ['one']; logHook2.preventTypes = ['one']; logHook3.preventTypes = ['one']; MessageLoop.installMessageHook(handler, logHook1); MessageLoop.installMessageHook(handler, logHook2); MessageLoop.installMessageHook(handler, logHook3); MessageLoop.sendMessage(handler, new Message('one')); MessageLoop.sendMessage(handler, new Message('two')); MessageLoop.sendMessage(handler, new Message('three')); expect(handler.messages).to.deep.equal(['two', 'three']); expect(logHook1.messages).to.deep.equal(['two', 'three']); expect(logHook2.messages).to.deep.equal(['two', 'three']); expect(logHook3.messages).to.deep.equal(['one', 'two', 'three']); }); it('should ignore exceptions in handlers', () => { let handler = new BadHandler(); let msg = new Message('one'); expect(() => { MessageLoop.sendMessage(handler, msg); }).to.not.throw(Error); }); it('should ignore exceptions in hooks', () => { let handler = new Handler(); let msg = new Message('one'); MessageLoop.installMessageHook(handler, (): boolean => { throw ''; }); expect(() => { MessageLoop.sendMessage(handler, msg); }).to.not.throw(Error); }); }); describe('postMessage()', () => { it('should post a message to the handler in the future', done => { let handler = new Handler(); expect(handler.messages).to.deep.equal([]); MessageLoop.postMessage(handler, new Message('one')); MessageLoop.postMessage(handler, new Message('two')); MessageLoop.postMessage(handler, new Message('three')); expect(handler.messages).to.deep.equal([]); defer(() => { expect(handler.messages).to.deep.equal(['one', 'two', 'three']); done(); }); }); it('should conflate a conflatable message', done => { let handler = new Handler(); let one = new Message('one'); let two = new Message('two'); let three = new ConflatableMessage('three'); expect(handler.messages).to.deep.equal([]); MessageLoop.postMessage(handler, one); MessageLoop.postMessage(handler, two); MessageLoop.postMessage(handler, three); MessageLoop.postMessage(handler, three); MessageLoop.postMessage(handler, three); MessageLoop.postMessage(handler, three); expect(handler.messages).to.deep.equal([]); defer(() => { expect(handler.messages).to.deep.equal(['one', 'two', 'three']); done(); }); }); it('should not conflate a non-conflatable message', done => { let handler = new Handler(); let cf1 = new Message('one'); let cf2 = new ConflatableMessage('one'); expect(handler.messages).to.deep.equal([]); MessageLoop.postMessage(handler, cf1); MessageLoop.postMessage(handler, cf2); expect(handler.messages).to.deep.equal([]); defer(() => { expect(handler.messages).to.deep.equal(['one', 'one']); done(); }); }); it('should not conflate messages for different handlers', done => { let h1 = new Handler(); let h2 = new Handler(); let msg = new ConflatableMessage('one'); MessageLoop.postMessage(h1, msg); MessageLoop.postMessage(h2, msg); defer(() => { expect(h1.messages).to.deep.equal(['one']); expect(h2.messages).to.deep.equal(['one']); done(); }); }); it('should obey global order of posted messages', done => { let handler1 = new GlobalHandler(); let handler2 = new GlobalHandler(); let handler3 = new GlobalHandler(); MessageLoop.postMessage(handler3, new Message('one')); MessageLoop.postMessage(handler1, new Message('two')); MessageLoop.postMessage(handler2, new Message('three')); MessageLoop.postMessage(handler1, new Message('A')); MessageLoop.postMessage(handler2, new Message('B')); MessageLoop.postMessage(handler3, new Message('C')); expect(handler1.messages).to.deep.equal([]); expect(handler2.messages).to.deep.equal([]); expect(handler3.messages).to.deep.equal([]); expect(GlobalHandler.messages).to.deep.equal([]); defer(() => { expect(GlobalHandler.messages).to.deep.equal([ 'one', 'two', 'three', 'A', 'B', 'C' ]); expect(handler1.messages).to.deep.equal(['two', 'A']); expect(handler2.messages).to.deep.equal(['three', 'B']); expect(handler3.messages).to.deep.equal(['one', 'C']); done(); }); }); }); describe('installMessageHook()', () => { it('should install a hook for a handler', () => { let handler = new Handler(); let logHook = new LogHook(); logHook.preventTypes = ['one']; MessageLoop.installMessageHook(handler, logHook); expect(handler.messages).to.deep.equal([]); MessageLoop.sendMessage(handler, new Message('one')); expect(handler.messages).to.deep.equal([]); }); it('should install a new hook in front of any others', () => { let handler = new Handler(); let logHook1 = new LogHook(); let logHook2 = new LogHook(); logHook1.preventTypes = ['one']; logHook2.preventTypes = ['two']; MessageLoop.installMessageHook(handler, logHook1); MessageLoop.sendMessage(handler, new Message('two')); MessageLoop.installMessageHook(handler, logHook2); MessageLoop.sendMessage(handler, new Message('two')); MessageLoop.sendMessage(handler, new Message('two')); MessageLoop.sendMessage(handler, new Message('three')); MessageLoop.sendMessage(handler, new Message('one')); expect(handler.messages).to.deep.equal(['two', 'three']); expect(logHook1.messages).to.deep.equal(['two', 'three', 'one']); expect(logHook2.messages).to.deep.equal(['two', 'two', 'three', 'one']); }); it('should not allow a hook to be installed multiple times', () => { let handler = new Handler(); let logHook1 = new LogHook(); let logHook2 = new LogHook(); MessageLoop.installMessageHook(handler, logHook1); MessageLoop.installMessageHook(handler, logHook2); MessageLoop.installMessageHook(handler, logHook1); MessageLoop.sendMessage(handler, new Message('one')); MessageLoop.sendMessage(handler, new Message('two')); expect(handler.messages).to.deep.equal(['one', 'two']); expect(logHook1.messages).to.deep.equal(['one', 'two']); expect(logHook2.messages).to.deep.equal(['one', 'two']); }); }); describe('removeMessageHook()', () => { it('should remove a previously installed hook', () => { let handler = new Handler(); let logHook1 = new LogHook(); let logHook2 = new LogHook(); logHook1.preventTypes = ['one']; logHook2.preventTypes = ['two']; MessageLoop.sendMessage(handler, new Message('one')); MessageLoop.sendMessage(handler, new Message('two')); MessageLoop.installMessageHook(handler, logHook1); MessageLoop.installMessageHook(handler, logHook2); MessageLoop.sendMessage(handler, new Message('one')); MessageLoop.sendMessage(handler, new Message('two')); MessageLoop.removeMessageHook(handler, logHook2); MessageLoop.removeMessageHook(handler, logHook1); MessageLoop.sendMessage(handler, new Message('one')); MessageLoop.sendMessage(handler, new Message('two')); expect(handler.messages).to.deep.equal(['one', 'two', 'one', 'two']); expect(logHook1.messages).to.deep.equal(['one']); expect(logHook2.messages).to.deep.equal(['one', 'two']); }); it('should be a no-op if the hook was not installed', () => { let handler = new Handler(); let logHook = new LogHook(); logHook.preventTypes = ['one']; MessageLoop.sendMessage(handler, new Message('one')); MessageLoop.removeMessageHook(handler, logHook); MessageLoop.sendMessage(handler, new Message('one')); expect(handler.messages).to.deep.equal(['one', 'one']); }); it('should be safe to remove a hook while dispatching', () => { let handler = new Handler(); let logHook1 = new LogHook(); let logHook2 = new LogHook(); let logHook3 = new LogHook(); let remHook: MessageHook = (handler: IMessageHandler, msg: Message) => { let result = logHook3.messageHook(handler, msg); MessageLoop.removeMessageHook(handler, remHook); return result; }; MessageLoop.installMessageHook(handler, logHook1); MessageLoop.installMessageHook(handler, remHook); MessageLoop.installMessageHook(handler, logHook2); MessageLoop.sendMessage(handler, new Message('one')); MessageLoop.sendMessage(handler, new Message('two')); MessageLoop.sendMessage(handler, new Message('three')); expect(handler.messages).to.deep.equal(['one', 'two', 'three']); expect(logHook1.messages).to.deep.equal(['one', 'two', 'three']); expect(logHook3.messages).to.deep.equal(['one']); expect(logHook2.messages).to.deep.equal(['one', 'two', 'three']); }); }); describe('clearData()', () => { it('should remove all message data associated with a handler', done => { let h1 = new Handler(); let h2 = new Handler(); let logHook = new LogHook(); MessageLoop.installMessageHook(h1, logHook); MessageLoop.postMessage(h1, new Message('one')); MessageLoop.postMessage(h2, new Message('one')); MessageLoop.postMessage(h1, new Message('two')); MessageLoop.postMessage(h2, new Message('two')); MessageLoop.postMessage(h1, new Message('three')); MessageLoop.postMessage(h2, new Message('three')); MessageLoop.clearData(h1); defer(() => { expect(h1.messages).to.deep.equal([]); expect(h2.messages).to.deep.equal(['one', 'two', 'three']); expect(logHook.messages).to.deep.equal([]); done(); }); }); }); describe('flush()', () => { it('should immediately process all posted messages', () => { let h1 = new Handler(); let h2 = new Handler(); MessageLoop.postMessage(h1, new Message('one')); MessageLoop.postMessage(h2, new Message('one')); MessageLoop.postMessage(h1, new Message('two')); MessageLoop.postMessage(h2, new Message('two')); MessageLoop.postMessage(h1, new Message('three')); MessageLoop.postMessage(h2, new Message('three')); MessageLoop.flush(); expect(h1.messages).to.deep.equal(['one', 'two', 'three']); expect(h2.messages).to.deep.equal(['one', 'two', 'three']); }); it('should ignore recursive calls', () => { let h1 = new Handler(); let h2 = new Handler(); MessageLoop.installMessageHook(h1, (h, m) => { if (m.type === 'two') { MessageLoop.postMessage(h, new Message('four')); MessageLoop.postMessage(h, new Message('five')); MessageLoop.postMessage(h, new Message('six')); MessageLoop.flush(); } return true; }); MessageLoop.postMessage(h1, new Message('one')); MessageLoop.postMessage(h2, new Message('one')); MessageLoop.postMessage(h1, new Message('two')); MessageLoop.postMessage(h2, new Message('two')); MessageLoop.postMessage(h1, new Message('three')); MessageLoop.postMessage(h2, new Message('three')); MessageLoop.flush(); expect(h1.messages).to.deep.equal(['one', 'two', 'three']); expect(h2.messages).to.deep.equal(['one', 'two', 'three']); MessageLoop.flush(); expect(h1.messages).to.deep.equal([ 'one', 'two', 'three', 'four', 'five', 'six' ]); expect(h2.messages).to.deep.equal(['one', 'two', 'three']); }); }); describe('getExceptionHandler()', () => { it('should default to an exception handler', () => { expect(MessageLoop.getExceptionHandler()).to.be.a('function'); }); }); describe('setExceptionHandler()', () => { afterEach(() => { MessageLoop.setExceptionHandler(console.error); }); it('should set the exception handler', () => { let handler = (err: Error) => { console.error(err); }; MessageLoop.setExceptionHandler(handler); expect(MessageLoop.getExceptionHandler()).to.equal(handler); }); it('should return the old exception handler', () => { let handler = (err: Error) => { console.error(err); }; let old1 = MessageLoop.setExceptionHandler(handler); let old2 = MessageLoop.setExceptionHandler(old1); expect(old1).to.equal(console.error); expect(old2).to.equal(handler); }); it('should invoke the exception handler on a message handler exception', () => { let called = false; let handler = new BadHandler(); MessageLoop.setExceptionHandler(() => { called = true; }); expect(called).to.equal(false); MessageLoop.sendMessage(handler, new Message('foo')); expect(called).to.equal(true); }); it('should invoke the exception handler on a message hook exception', () => { let called = false; let handler = new Handler(); MessageLoop.setExceptionHandler(() => { called = true; }); expect(called).to.equal(false); MessageLoop.sendMessage(handler, new Message('foo')); expect(called).to.equal(false); MessageLoop.installMessageHook(handler, () => { throw 'error'; }); expect(called).to.equal(false); MessageLoop.sendMessage(handler, new Message('foo')); expect(called).to.equal(true); }); }); }); }); lumino-2021.12.13/packages/messaging/tests/tsconfig.json000066400000000000000000000007701415564225700230310ustar00rootroot00000000000000{ "compilerOptions": { "declaration": false, "noImplicitAny": true, "noEmitOnError": true, "noUnusedLocals": true, "strictNullChecks": true, "module": "commonjs", "moduleResolution": "node", "target": "ES5", "outDir": "build", "lib": ["ES5", "DOM"], "types": ["chai", "mocha", "@types/node"], "rootDir": "src" }, "include": ["src/*"], "references": [ { "path": "../../algorithm" }, { "path": "../../collections" } ] } lumino-2021.12.13/packages/messaging/tests/webpack.config.js000066400000000000000000000003061415564225700235330ustar00rootroot00000000000000var path = require('path'); module.exports = { entry: './build/index.spec.js', mode: 'development', output: { filename: './build/bundle.test.js', path: path.resolve(__dirname) } }; lumino-2021.12.13/packages/messaging/tsconfig.json000066400000000000000000000011641415564225700216650ustar00rootroot00000000000000{ "compilerOptions": { "composite": true, "sourceMap": true, "declaration": true, "declarationDir": "./types", "declarationMap": true, "noImplicitAny": true, "noEmitOnError": true, "noUnusedLocals": true, "strictNullChecks": true, "module": "ES6", "moduleResolution": "node", "target": "ES5", "outDir": "lib", "lib": ["ES5", "ES2015.Collection", "DOM"], "importHelpers": true, "types": ["@types/node"], "rootDir": "src" }, "include": ["src/*"], "references": [ { "path": "../algorithm" }, { "path": "../collections" } ] } lumino-2021.12.13/packages/polling/000077500000000000000000000000001415564225700166435ustar00rootroot00000000000000lumino-2021.12.13/packages/polling/api-extractor.json000066400000000000000000000014611415564225700223220ustar00rootroot00000000000000/** * Config file for API Extractor. For more info, please visit: https://api-extractor.com */ { "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", /** * Optionally specifies another JSON config file that this file extends from. This provides a way for * standard settings to be shared across multiple projects. * * If the path starts with "./" or "../", the path is resolved relative to the folder of the file that contains * the "extends" field. Otherwise, the first path segment is interpreted as an NPM package name, and will be * resolved using NodeJS require(). * * SUPPORTED TOKENS: none * DEFAULT VALUE: "" */ "extends": "../../api-extractor-base.json" // "extends": "my-package/include/api-extractor-base.json" } lumino-2021.12.13/packages/polling/package.json000066400000000000000000000051311415564225700211310ustar00rootroot00000000000000{ "name": "@lumino/polling", "version": "1.9.1", "description": "Lumino Polling", "homepage": "https://github.com/jupyterlab/lumino", "bugs": { "url": "https://github.com/jupyterlab/lumino/issues" }, "repository": { "type": "git", "url": "https://github.com/jupyterlab/lumino.git" }, "license": "BSD-3-Clause", "author": "A. T. Darian ", "contributors": [ "A. T. Darian ", "Ian Rose ", "Jason Grout ", "Steven Silvester " ], "main": "dist/index.js", "jsdelivr": "dist/index.min.js", "unpkg": "dist/index.min.js", "module": "dist/index.es6", "types": "types/index.d.ts", "files": [ "dist/*", "src/*", "types/*" ], "scripts": { "api": "api-extractor run --local --verbose", "build": "npm run build:src && rollup -c", "build:src": "tsc --build", "build:test": "tsc --build tests && cd tests && webpack", "clean": "rimraf ./lib && rimraf *.tsbuildinfo && rimraf ./types && rimraf ./dist", "clean:test": "rimraf tests/build", "docs": "typedoc --options tdoptions.json src", "minimize": "terser dist/index.js -c -m --source-map \"content='dist/index.js.map',url='index.min.js.map'\" -o dist/index.min.js", "test": "npm run test:firefox-headless", "test:chrome": "cd tests && karma start --browsers=Chrome", "test:chrome-headless": "cd tests && karma start --browsers=ChromeHeadless", "test:firefox": "cd tests && karma start --browsers=Firefox", "test:firefox-headless": "cd tests && karma start --browsers=FirefoxHeadless", "test:ie": "cd tests && karma start --browsers=IE", "watch": "tsc --build --watch" }, "dependencies": { "@lumino/coreutils": "^1.11.1", "@lumino/disposable": "^1.10.1", "@lumino/signaling": "^1.10.1" }, "devDependencies": { "@microsoft/api-extractor": "^7.6.0", "@types/chai": "^3.4.35", "@types/mocha": "^2.2.39", "@types/node": "^12.12.17", "chai": "^4.3.4", "karma": "^6.3.4", "karma-chrome-launcher": "^3.1.0", "karma-firefox-launcher": "^2.1.1", "karma-ie-launcher": "^1.0.0", "karma-mocha": "^2.0.1", "karma-mocha-reporter": "^2.2.5", "mocha": "^9.0.3", "rimraf": "^3.0.2", "rollup": "^2.56.0", "rollup-plugin-node-resolve": "^5.2.0", "rollup-plugin-postcss": "^4.0.0", "rollup-plugin-sourcemaps": "^0.6.3", "terser": "^5.7.1", "tslib": "^2.3.0", "typescript": "~3.6.0", "webpack": "^4.41.3", "webpack-cli": "^3.3.10" }, "publishConfig": { "access": "public" } } lumino-2021.12.13/packages/polling/rollup.config.js000066400000000000000000000015231415564225700217630ustar00rootroot00000000000000import nodeResolve from 'rollup-plugin-node-resolve'; import sourcemaps from 'rollup-plugin-sourcemaps'; import postcss from 'rollup-plugin-postcss'; const pkg = require('./package.json'); const globals = id => id.indexOf('@lumino/') === 0 ? id.replace('@lumino/', 'lumino_') : id; export default [ { input: 'lib/index', external: id => pkg.dependencies && !!pkg.dependencies[id], output: [ { file: pkg.main, globals, format: 'umd', sourcemap: true, name: pkg.name }, { file: pkg.module + '.js', format: 'es', sourcemap: true, name: pkg.name } ], plugins: [ nodeResolve({ preferBuiltins: true }), sourcemaps(), postcss({ extensions: ['.css'], minimize: true }) ] } ]; lumino-2021.12.13/packages/polling/src/000077500000000000000000000000001415564225700174325ustar00rootroot00000000000000lumino-2021.12.13/packages/polling/src/index.ts000066400000000000000000000112141415564225700211100ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. import { IDisposable } from '@lumino/disposable'; import { ISignal } from '@lumino/signaling'; export { Poll } from './poll'; export { Debouncer, RateLimiter, Throttler } from './ratelimiter'; /** * A readonly poll that calls an asynchronous function with each tick. * * @typeparam T - The resolved type of the factory's promises. * * @typeparam U - The rejected type of the factory's promises. * * @typeparam V - The type to extend the phases supported by a poll. */ export interface IPoll { /** * A signal emitted when the poll is disposed. */ readonly disposed: ISignal; /** * The polling frequency data. */ readonly frequency: IPoll.Frequency; /** * Whether the poll is disposed. */ readonly isDisposed: boolean; /** * The name of the poll. */ readonly name: string; /** * The poll state, which is the content of the currently-scheduled poll tick. */ readonly state: IPoll.State; /** * A promise that resolves when the currently-scheduled tick completes. * * #### Notes * Usually this will resolve after `state.interval` milliseconds from * `state.timestamp`. It can resolve earlier if the user starts or refreshes the * poll, etc. */ readonly tick: Promise>; /** * A signal emitted when the poll state changes, i.e., a new tick is scheduled. */ readonly ticked: ISignal, IPoll.State>; } /** * A namespace for `IPoll` types. */ export namespace IPoll { /** * The polling frequency parameters. * * #### Notes * We implement the "decorrelated jitter" strategy from * https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/. * Essentially, if consecutive retries are needed, we choose an integer: * `sleep = min(max, rand(interval, backoff * sleep))` * This ensures that the poll is never less than `interval`, and nicely * spreads out retries for consecutive tries. Over time, if (interval < max), * the random number will be above `max` about (1 - 1/backoff) of the time * (sleeping the `max`), and the rest of the time the sleep will be a random * number below `max`, decorrelating our trigger time from other pollers. */ export type Frequency = { /** * Whether poll frequency backs off (boolean) or the backoff growth rate * (float > 1). * * #### Notes * If `true`, the default backoff growth rate is `3`. */ readonly backoff: boolean | number; /** * The basic polling interval in milliseconds (integer). */ readonly interval: number; /** * The maximum milliseconds (integer) between poll requests. */ readonly max: number; }; /** * The phase of the poll when the current tick was scheduled. * * @typeparam T - A type for any additional tick phases a poll supports. */ export type Phase = | T | 'constructed' | 'disposed' | 'reconnected' | 'refreshed' | 'rejected' | 'resolved' | 'standby' | 'started' | 'stopped'; /** * Definition of poll state at any given time. * * @typeparam T - The resolved type of the factory's promises. * * @typeparam U - The rejected type of the factory's promises. * * @typeparam V - The type to extend the phases supported by a poll. */ export type State = { /** * The number of milliseconds until the current tick resolves. */ readonly interval: number; /** * The payload of the last poll resolution or rejection. * * #### Notes * The payload is `null` unless the `phase` is `'reconnected`, `'resolved'`, * or `'rejected'`. Its type is `T` for resolutions and `U` for rejections. */ readonly payload: T | U | null; /** * The current poll phase. */ readonly phase: Phase; /** * The timestamp for when this tick was scheduled. */ readonly timestamp: number; }; } /** * A function whose invocations are rate limited and can be stopped after * invocation before it has fired. * * @typeparam T - The resolved type of the underlying function. Defaults to any. * * @typeparam U - The rejected type of the underlying function. Defaults to any. */ export interface IRateLimiter extends IDisposable { /** * The rate limit in milliseconds. */ readonly limit: number; /** * Invoke the rate limited function. */ invoke(): Promise; /** * Stop the function if it is mid-flight. */ stop(): Promise; } lumino-2021.12.13/packages/polling/src/poll.ts000066400000000000000000000332061415564225700207540ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. import { JSONExt, PromiseDelegate } from '@lumino/coreutils'; import { IObservableDisposable } from '@lumino/disposable'; import { ISignal, Signal } from '@lumino/signaling'; import { IPoll } from './index'; /** * A function to defer an action immediately. */ const defer = typeof requestAnimationFrame === 'function' ? requestAnimationFrame : setImmediate; /** * A function to cancel a deferred action. */ const cancel: (timeout: any) => void = typeof cancelAnimationFrame === 'function' ? cancelAnimationFrame : clearImmediate; /** * A class that wraps an asynchronous function to poll at a regular interval * with exponential increases to the interval length if the poll fails. * * @typeparam T - The resolved type of the factory's promises. * Defaults to `any`. * * @typeparam U - The rejected type of the factory's promises. * Defaults to `any`. * * @typeparam V - An optional type to extend the phases supported by a poll. * Defaults to `standby`, which already exists in the `Phase` type. */ export class Poll implements IObservableDisposable, IPoll { /** * Instantiate a new poll with exponential backoff in case of failure. * * @param options - The poll instantiation options. */ constructor(options: Poll.IOptions) { this._factory = options.factory; this._standby = options.standby || Private.DEFAULT_STANDBY; this._state = { ...Private.DEFAULT_STATE, timestamp: new Date().getTime() }; // Normalize poll frequency `max` to be the greater of // default `max`, `options.frequency.max`, or `options.frequency.interval`. const frequency = options.frequency || {}; const max = Math.max( frequency.interval || 0, frequency.max || 0, Private.DEFAULT_FREQUENCY.max ); this.frequency = { ...Private.DEFAULT_FREQUENCY, ...frequency, ...{ max } }; this.name = options.name || Private.DEFAULT_NAME; if ('auto' in options ? options.auto : true) { defer(() => void this.start()); } } /** * The name of the poll. */ readonly name: string; /** * A signal emitted when the poll is disposed. */ get disposed(): ISignal { return this._disposed; } /** * The polling frequency parameters. */ get frequency(): IPoll.Frequency { return this._frequency; } set frequency(frequency: IPoll.Frequency) { if (this.isDisposed || JSONExt.deepEqual(frequency, this.frequency || {})) { return; } let { backoff, interval, max } = frequency; interval = Math.round(interval); max = Math.round(max); if (typeof backoff === 'number' && backoff < 1) { throw new Error('Poll backoff growth factor must be at least 1'); } if ((interval < 0 || interval > max) && interval !== Poll.NEVER) { throw new Error('Poll interval must be between 0 and max'); } if (max > Poll.MAX_INTERVAL && max !== Poll.NEVER) { throw new Error(`Max interval must be less than ${Poll.MAX_INTERVAL}`); } this._frequency = { backoff, interval, max }; } /** * Whether the poll is disposed. */ get isDisposed(): boolean { return this.state.phase === 'disposed'; } /** * Indicates when the poll switches to standby. */ get standby(): Poll.Standby | (() => boolean | Poll.Standby) { return this._standby; } set standby(standby: Poll.Standby | (() => boolean | Poll.Standby)) { if (this.isDisposed || this.standby === standby) { return; } this._standby = standby; } /** * The poll state, which is the content of the current poll tick. */ get state(): IPoll.State { return this._state; } /** * A promise that resolves when the poll next ticks. */ get tick(): Promise { return this._tick.promise; } /** * A signal emitted when the poll ticks and fires off a new request. */ get ticked(): ISignal> { return this._ticked; } /** * Dispose the poll. */ dispose(): void { if (this.isDisposed) { return; } this._state = { ...Private.DISPOSED_STATE, timestamp: new Date().getTime() }; this._tick.promise.catch(_ => undefined); this._tick.reject(new Error(`Poll (${this.name}) is disposed.`)); this._disposed.emit(undefined); Signal.clearData(this); } /** * Refreshes the poll. Schedules `refreshed` tick if necessary. * * @returns A promise that resolves after tick is scheduled and never rejects. * * #### Notes * The returned promise resolves after the tick is scheduled, but before * the polling action is run. To wait until after the poll action executes, * await the `poll.tick` promise: `await poll.refresh(); await poll.tick;` */ refresh(): Promise { return this.schedule({ cancel: ({ phase }) => phase === 'refreshed', interval: Poll.IMMEDIATE, phase: 'refreshed' }); } /** * Schedule the next poll tick. * * @param next - The next poll state data to schedule. Defaults to standby. * * @param next.cancel - Cancels state transition if function returns `true`. * * @returns A promise that resolves when the next poll state is active. * * #### Notes * This method is not meant to be invoked by user code typically. It is public * to allow poll instances to be composed into classes that schedule ticks. */ async schedule( next: Partial< IPoll.State & { cancel: (last: IPoll.State) => boolean } > = {} ): Promise { if (this.isDisposed) { return; } // Check if the phase transition should be canceled. if (next.cancel && next.cancel(this.state)) { return; } // Update poll state. const last = this.state; const pending = this._tick; const scheduled = new PromiseDelegate(); const state = { interval: this.frequency.interval, payload: null, phase: 'standby', timestamp: new Date().getTime(), ...next } as IPoll.State; this._state = state; this._tick = scheduled; // Clear the schedule if possible. if (last.interval === Poll.IMMEDIATE) { cancel(this._timeout); } else { clearTimeout(this._timeout); } // Emit ticked signal, resolve pending promise, and await its settlement. this._ticked.emit(this.state); pending.resolve(this); await pending.promise; // Schedule next execution and cache its timeout handle. const execute = () => { if (this.isDisposed || this.tick !== scheduled.promise) { return; } this._execute(); }; this._timeout = state.interval === Poll.IMMEDIATE ? defer(execute) : state.interval === Poll.NEVER ? -1 : setTimeout(execute, state.interval); } /** * Starts the poll. Schedules `started` tick if necessary. * * @returns A promise that resolves after tick is scheduled and never rejects. */ start(): Promise { return this.schedule({ cancel: ({ phase }) => phase !== 'constructed' && phase !== 'standby' && phase !== 'stopped', interval: Poll.IMMEDIATE, phase: 'started' }); } /** * Stops the poll. Schedules `stopped` tick if necessary. * * @returns A promise that resolves after tick is scheduled and never rejects. */ stop(): Promise { return this.schedule({ cancel: ({ phase }) => phase === 'stopped', interval: Poll.NEVER, phase: 'stopped' }); } /** * Execute a new poll factory promise or stand by if necessary. */ private _execute(): void { let standby = typeof this.standby === 'function' ? this.standby() : this.standby; standby = standby === 'never' ? false : standby === 'when-hidden' ? !!(typeof document !== 'undefined' && document && document.hidden) : standby; // If in standby mode schedule next tick without calling the factory. if (standby) { void this.schedule(); return; } const pending = this.tick; this._factory(this.state) .then((resolved: T) => { if (this.isDisposed || this.tick !== pending) { return; } void this.schedule({ payload: resolved, phase: this.state.phase === 'rejected' ? 'reconnected' : 'resolved' }); }) .catch((rejected: U) => { if (this.isDisposed || this.tick !== pending) { return; } void this.schedule({ interval: Private.sleep(this.frequency, this.state), payload: rejected, phase: 'rejected' }); }); } private _disposed = new Signal(this); private _factory: Poll.Factory; private _frequency: IPoll.Frequency; private _standby: Poll.Standby | (() => boolean | Poll.Standby); private _state: IPoll.State; private _tick = new PromiseDelegate(); private _ticked = new Signal>(this); private _timeout: any = -1; } /** * A namespace for `Poll` types, interfaces, and statics. */ export namespace Poll { /** * A promise factory that returns an individual poll request. * * @typeparam T - The resolved type of the factory's promises. * * @typeparam U - The rejected type of the factory's promises. * * @typeparam V - The type to extend the phases supported by a poll. */ export type Factory = ( state: IPoll.State ) => Promise; /** * Indicates when the poll switches to standby. */ export type Standby = 'never' | 'when-hidden'; /** * Instantiation options for polls. * * @typeparam T - The resolved type of the factory's promises. * * @typeparam U - The rejected type of the factory's promises. * * @typeparam V - The type to extend the phases supported by a poll. */ export interface IOptions { /** * Whether to begin polling automatically; defaults to `true`. */ auto?: boolean; /** * A factory function that is passed a poll tick and returns a poll promise. */ factory: Factory; /** * The polling frequency parameters. */ frequency?: Partial; /** * The name of the poll. * Defaults to `'unknown'`. */ name?: string; /** * Indicates when the poll switches to standby or a function that returns * a boolean or a `Poll.Standby` value to indicate whether to stand by. * Defaults to `'when-hidden'`. * * #### Notes * If a function is passed in, for any given context, it should be * idempotent and safe to call multiple times. It will be called before each * tick execution, but may be called by clients as well. */ standby?: Standby | (() => boolean | Standby); } /** * An interval value that indicates the poll should tick immediately. */ export const IMMEDIATE = 0; /** * Delays are 32-bit integers in many browsers so intervals need to be capped. * * #### Notes * https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout#Maximum_delay_value */ export const MAX_INTERVAL = 2147483647; /** * An interval value that indicates the poll should never tick. */ export const NEVER = Infinity; } /** * A namespace for private module data. */ namespace Private { /** * The default backoff growth rate if `backoff` is `true`. */ export const DEFAULT_BACKOFF = 3; /** * The default polling frequency. */ export const DEFAULT_FREQUENCY: IPoll.Frequency = { backoff: true, interval: 1000, max: 30 * 1000 }; /** * The default poll name. */ export const DEFAULT_NAME = 'unknown'; /** * The default poll standby behavior. */ export const DEFAULT_STANDBY: Poll.Standby = 'when-hidden'; /** * The first poll tick state's default values superseded in constructor. */ export const DEFAULT_STATE: IPoll.State = { interval: Poll.NEVER, payload: null, phase: 'constructed', timestamp: new Date(0).getTime() }; /** * The disposed tick state values. */ export const DISPOSED_STATE: IPoll.State = { interval: Poll.NEVER, payload: null, phase: 'disposed', timestamp: new Date(0).getTime() }; /** * Get a random integer between min and max, inclusive of both. * * #### Notes * From * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random#Getting_a_random_integer_between_two_values_inclusive * * From the MDN page: It might be tempting to use Math.round() to accomplish * that, but doing so would cause your random numbers to follow a non-uniform * distribution, which may not be acceptable for your needs. */ function getRandomIntInclusive(min: number, max: number) { min = Math.ceil(min); max = Math.floor(max); return Math.floor(Math.random() * (max - min + 1)) + min; } /** * Returns the number of milliseconds to sleep before the next tick. * * @param frequency - The poll's base frequency. * @param last - The poll's last tick. */ export function sleep( frequency: IPoll.Frequency, last: IPoll.State ): number { const { backoff, interval, max } = frequency; if (interval === Poll.NEVER) { return interval; } const growth = backoff === true ? DEFAULT_BACKOFF : backoff === false ? 1 : backoff; const random = getRandomIntInclusive(interval, last.interval * growth); return Math.min(max, random); } } lumino-2021.12.13/packages/polling/src/ratelimiter.ts000066400000000000000000000112131415564225700223210ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. import { PromiseDelegate } from '@lumino/coreutils'; import { IRateLimiter } from './index'; import { Poll } from './poll'; /** * A base class to implement rate limiters with different invocation strategies. * * @typeparam T - The resolved type of the underlying function. * * @typeparam U - The rejected type of the underlying function. */ export abstract class RateLimiter implements IRateLimiter { /** * Instantiate a rate limiter. * * @param fn - The function to rate limit. * * @param limit - The rate limit; defaults to 500ms. */ constructor(fn: () => T | Promise, limit = 500) { this.limit = limit; this.poll = new Poll({ auto: false, factory: async () => await fn(), frequency: { backoff: false, interval: Poll.NEVER, max: Poll.NEVER }, standby: 'never' }); this.payload = new PromiseDelegate(); this.poll.ticked.connect((_, state) => { const { payload } = this; if (state.phase === 'resolved') { this.payload = new PromiseDelegate(); payload!.resolve(state.payload as T); return; } if (state.phase === 'rejected' || state.phase === 'stopped') { this.payload = new PromiseDelegate(); payload!.promise.catch(_ => undefined); payload!.reject(state.payload as U); return; } }, this); } /** * Whether the rate limiter is disposed. */ get isDisposed(): boolean { return this.payload === null; } /** * Disposes the rate limiter. */ dispose(): void { if (this.isDisposed) { return; } this.payload = null; this.poll.dispose(); } /** * The rate limit in milliseconds. */ readonly limit: number; /** * Invoke the rate limited function. */ abstract invoke(): Promise; /** * Stop the function if it is mid-flight. */ async stop(): Promise { return this.poll.stop(); } /** * A promise that resolves on each successful invocation. */ protected payload: PromiseDelegate | null = null; /** * The underlying poll instance used by the rate limiter. */ protected poll: Poll; } /** * Wraps and debounces a function that can be called multiple times and only * executes the underlying function one `interval` after the last invocation. * * @typeparam T - The resolved type of the underlying function. Defaults to any. * * @typeparam U - The rejected type of the underlying function. Defaults to any. */ export class Debouncer extends RateLimiter { /** * Invokes the function and only executes after rate limit has elapsed. * Each invocation resets the timer. */ invoke(): Promise { void this.poll.schedule({ interval: this.limit, phase: 'invoked' }); return this.payload!.promise; } } /** * Wraps and throttles a function that can be called multiple times and only * executes the underlying function once per `interval`. * * @typeparam T - The resolved type of the underlying function. Defaults to any. * * @typeparam U - The rejected type of the underlying function. Defaults to any. */ export class Throttler extends RateLimiter { /** * Instantiate a throttler. * * @param fn - The function being throttled. * * @param options - Throttling configuration or throttling limit in ms. * * #### Notes * The `edge` defaults to `leading`; the `limit` defaults to `500`. */ constructor(fn: () => T | Promise, options?: Throttler.IOptions | number) { super(fn, typeof options === 'number' ? options : options && options.limit); let edge: 'leading' | 'trailing' = 'leading'; if (typeof options !== 'number') { options = options || {}; edge = 'edge' in options ? options.edge! : edge; } this._interval = edge === 'trailing' ? this.limit : Poll.IMMEDIATE; } /** * Throttles function invocations if one is currently in flight. */ invoke(): Promise { if (this.poll.state.phase !== 'invoked') { void this.poll.schedule({ interval: this._interval, phase: 'invoked' }); } return this.payload!.promise; } private _interval: number; } /** * A namespace for `Throttler` interfaces. */ export namespace Throttler { /** * Instantiation options for a `Throttler`. */ export interface IOptions { /** * The throttling limit; defaults to 500ms. */ limit?: number; /** * Whether to invoke at the leading or trailing edge of throttle cycle. * Defaults to `leading`. */ edge?: 'leading' | 'trailing'; } } lumino-2021.12.13/packages/polling/tdoptions.json000066400000000000000000000005161415564225700215630ustar00rootroot00000000000000{ "excludeNotExported": true, "mode": "file", "target": "es5", "module": "es5", "lib": [ "lib.es2015.d.ts", "lib.es2015.collection.d.ts", "lib.es2015.promise.d.ts", "lib.dom.d.ts" ], "out": "../../docs/source/api/polling", "baseUrl": ".", "paths": { "@lumino/*": ["node_modules/@lumino/*"] } } lumino-2021.12.13/packages/polling/tests/000077500000000000000000000000001415564225700200055ustar00rootroot00000000000000lumino-2021.12.13/packages/polling/tests/karma.conf.js000066400000000000000000000005051415564225700223620ustar00rootroot00000000000000module.exports = function (config) { config.set({ basePath: '.', frameworks: ['mocha'], reporters: ['mocha'], files: ['build/bundle.test.js'], port: 9876, colors: true, singleRun: true, browserNoActivityTimeout: 30000, failOnEmptyTestSuite: false, logLevel: config.LOG_INFO }); }; lumino-2021.12.13/packages/polling/tests/src/000077500000000000000000000000001415564225700205745ustar00rootroot00000000000000lumino-2021.12.13/packages/polling/tests/src/index.spec.ts000066400000000000000000000002331415564225700232020ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. import './poll.spec'; import './ratelimiter.spec'; lumino-2021.12.13/packages/polling/tests/src/poll.spec.ts000066400000000000000000000400251415564225700230440ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. import { expect } from 'chai'; import { IPoll, Poll } from '@lumino/polling'; /** * Return a promise that resolves in the given milliseconds with the given value. */ function sleep(milliseconds: number = 0, value?: T): Promise { return new Promise((resolve, reject) => { setTimeout(() => { resolve(value); }, milliseconds); }); } describe('Poll', () => { let poll: Poll; afterEach(() => { poll.dispose(); }); describe('#constructor()', () => { it('should create a poll', () => { poll = new Poll({ auto: false, factory: () => Promise.resolve(), name: '@lumino/polling:Poll#constructor()-1' }); expect(poll).to.be.an.instanceof(Poll); }); it('should start polling automatically', async () => { const expected = 'started resolved'; const ticker: IPoll.Phase[] = []; poll = new Poll({ name: '@lumino/polling:Poll#constructor()-2', frequency: { interval: 100 }, factory: () => Promise.resolve() }); poll.ticked.connect((_, tick) => { ticker.push(tick.phase); }); expect(poll.state.phase).to.equal('constructed'); await poll.tick; expect(poll.state.phase).to.equal('started'); await poll.tick; expect(poll.state.phase).to.equal('resolved'); expect(ticker.join(' ')).to.equal(expected); }); it('should not poll if `auto` is set to false', async () => { const expected = ''; const ticker: IPoll.Phase[] = []; poll = new Poll({ auto: false, name: '@lumino/polling:Poll#constructor()-2', frequency: { interval: 100 }, factory: () => Promise.resolve() }); poll.ticked.connect((_, tick) => { ticker.push(tick.phase); }); expect(poll.state.phase).to.equal('constructed'); await sleep(250); // Sleep for longer than the interval. expect(ticker.join(' ')).to.equal(expected); }); describe('#options.frequency', () => { it('should set frequency interval', () => { const interval = 9000; poll = new Poll({ factory: () => Promise.resolve(), frequency: { interval }, name: '@lumino/polling:Poll#frequency:interval-1' }); expect(poll.frequency.interval).to.equal(interval); }); it('should default frequency interval to `1000`', () => { poll = new Poll({ factory: () => Promise.resolve(), frequency: {}, name: '@lumino/polling:Poll#frequency:interval-2' }); expect(poll.frequency.interval).to.equal(1000); }); it('should set backoff', () => { const backoff = false; poll = new Poll({ factory: () => Promise.resolve(), frequency: { backoff }, name: '@lumino/polling:Poll#frequency:backoff-1' }); expect(poll.frequency.backoff).to.equal(backoff); }); it('should default backoff to `true`', () => { poll = new Poll({ factory: () => Promise.resolve(), name: '@lumino/polling:Poll#frequency:backoff-2' }); expect(poll.frequency.backoff).to.equal(true); }); it('should set max value', () => { const max = 200000; poll = new Poll({ factory: () => Promise.resolve(), frequency: { max }, name: '@lumino/polling:Poll#max-1' }); expect(poll.frequency.max).to.equal(200000); }); it('should default max to 30s', () => { const interval = 500; const max = 30 * 1000; poll = new Poll({ frequency: { interval }, factory: () => Promise.resolve(), name: '@lumino/polling:Poll#frequency:max-2' }); expect(poll.frequency.max).to.equal(max); }); it('should normalize max to be biggest of default, max, interval', () => { const interval = 25 * 1000; const max = 20 * 1000; poll = new Poll({ frequency: { interval }, factory: () => Promise.resolve(), name: '@lumino/polling:Poll#frequency:max-3' }); expect(poll.frequency.max).to.not.equal(max); expect(poll.frequency.max).to.equal(30 * 1000); // Poll default max }); it('should normalize max to be biggest of default, max, interval', () => { const interval = 40 * 1000; const max = 20 * 1000; poll = new Poll({ frequency: { interval }, factory: () => Promise.resolve(), name: '@lumino/polling:Poll#frequency:max-4' }); expect(poll.frequency.max).to.not.equal(max); expect(poll.frequency.max).to.equal(interval); }); }); }); describe('#name', () => { it('should be set to value passed in during instantation', () => { const factory = () => Promise.resolve(); const name = '@lumino/polling:Poll#name-1'; poll = new Poll({ factory, name }); expect(poll.name).to.equal(name); }); it('should default to `unknown`', () => { poll = new Poll({ factory: () => Promise.resolve() }); expect(poll.name).to.equal('unknown'); }); }); describe('#disposed', () => { it('should emit when the poll is disposed', () => { poll = new Poll({ factory: () => Promise.resolve(), name: '@lumino/polling:Poll#disposed-1' }); let disposed = false; poll.disposed.connect(() => { disposed = true; }); poll.dispose(); expect(disposed).to.equal(true); }); }); describe('#isDisposed', () => { it('should indicate whether the poll is disposed', () => { poll = new Poll({ factory: () => Promise.resolve(), name: '@lumino/polling:Poll#isDisposed-1' }); expect(poll.isDisposed).to.equal(false); poll.dispose(); expect(poll.isDisposed).to.equal(true); }); }); describe('#tick', () => { it('should resolve after a tick', async () => { poll = new Poll({ auto: false, factory: () => Promise.resolve(), frequency: { interval: 200, backoff: false }, name: '@lumino/polling:Poll#tick-1' }); const expected = 'started resolved resolved'; const ticker: IPoll.Phase[] = []; const tock = (poll: Poll) => { ticker.push(poll.state.phase); poll.tick.then(tock).catch(() => undefined); }; void poll.tick.then(tock); void poll.start(); await sleep(300); // Sleep for longer than the interval. expect(ticker.join(' ')).to.equal(expected); }); it('should resolve after `ticked` emits in lock step', async () => { poll = new Poll({ factory: () => Math.random() > 0.5 ? Promise.resolve() : Promise.reject(), frequency: { interval: 0, backoff: false }, name: '@lumino/polling:Poll#tick-2' }); const ticker: IPoll.Phase[] = []; const tocker: IPoll.Phase[] = []; poll.ticked.connect(async (_, state) => { ticker.push(state.phase); expect(ticker.length).to.equal(tocker.length + 1); }); const tock = async (poll: Poll) => { tocker.push(poll.state.phase); expect(ticker.join(' ')).to.equal(tocker.join(' ')); poll.tick.then(tock).catch(() => undefined); }; // Kick off the promise listener, but void its settlement to verify that // the poll's internal sync of the promise and the signal is correct. void poll.tick.then(tock); await poll.stop(); await poll.start(); await poll.tick; await poll.refresh(); await poll.tick; await poll.refresh(); await poll.tick; await poll.refresh(); await poll.tick; await poll.stop(); await poll.start(); await poll.tick; await sleep(100); await poll.tick; expect(ticker.join(' ')).to.equal(tocker.join(' ')); }); }); describe('#ticked', () => { it('should emit a tick identical to the poll state', async () => { poll = new Poll({ factory: () => Promise.resolve(), frequency: { interval: 100, backoff: false }, name: '@lumino/polling:Poll#ticked-3' }); poll.ticked.connect((_, tick) => { expect(tick).to.equal(poll.state); }); await sleep(250); }); }); describe('#dispose()', () => { it('should dispose the poll and be safe to call repeatedly', async () => { let rejected = false; let tick: Promise; poll = new Poll({ name: '@lumino/polling:Poll#dispose()-1', factory: () => Promise.resolve() }); tick = poll.tick; expect(poll.isDisposed).to.equal(false); poll.dispose(); expect(poll.isDisposed).to.equal(true); try { await tick; } catch (error) { rejected = true; } poll.dispose(); expect(rejected).to.equal(true); }); }); describe('#refresh()', () => { it('should refresh the poll, superseding `started`', async () => { const expected = 'refreshed resolved'; const ticker: IPoll.Phase[] = []; poll = new Poll({ name: '@lumino/polling:Poll#refresh()-1', frequency: { interval: 100 }, factory: () => Promise.resolve() }); poll.ticked.connect((_, tick) => { ticker.push(tick.phase); }); expect(poll.state.phase).to.equal('constructed'); await poll.refresh(); expect(poll.state.phase).to.equal('refreshed'); await poll.tick; expect(poll.state.phase).to.equal('resolved'); expect(ticker.join(' ')).to.equal(expected); }); it('should be safe to call multiple times', async () => { const expected = 'started resolved refreshed resolved'; const ticker: IPoll.Phase[] = []; poll = new Poll({ name: '@lumino/polling:Poll#refresh()-2', frequency: { interval: 100 }, factory: () => Promise.resolve() }); poll.ticked.connect((_, tick) => { ticker.push(tick.phase); }); expect(poll.state.phase).to.equal('constructed'); await poll.tick; expect(poll.state.phase).to.equal('started'); await poll.tick; expect(poll.state.phase).to.equal('resolved'); await poll.refresh(); expect(poll.state.phase).to.equal('refreshed'); await poll.refresh(); expect(poll.state.phase).to.equal('refreshed'); await poll.refresh(); expect(poll.state.phase).to.equal('refreshed'); await poll.tick; expect(poll.state.phase).to.equal('resolved'); expect(ticker.join(' ')).to.equal(expected); }); }); describe('#start()', () => { it('should start the poll if it is stopped', async () => { const expected = 'stopped started resolved'; const ticker: IPoll.Phase[] = []; poll = new Poll({ name: '@lumino/polling:Poll#start()-1', frequency: { interval: 100 }, factory: () => Promise.resolve() }); poll.ticked.connect((_, tick) => { ticker.push(tick.phase); }); await poll.stop(); expect(poll.state.phase).to.equal('stopped'); await poll.start(); expect(poll.state.phase).to.equal('started'); await poll.tick; expect(poll.state.phase).to.equal('resolved'); expect(ticker.join(' ')).to.equal(expected); }); it('be safe to call multiple times and no-op if unnecessary', async () => { const expected = 'started resolved stopped started resolved'; const ticker: IPoll.Phase[] = []; poll = new Poll({ auto: false, name: '@lumino/polling:Poll#start()-2', frequency: { interval: 100 }, factory: () => Promise.resolve() }); poll.ticked.connect((_, tick) => { ticker.push(tick.phase); }); expect(poll.state.phase).to.equal('constructed'); await poll.start(); expect(poll.state.phase).to.equal('started'); await poll.start(); expect(poll.state.phase).to.equal('started'); await poll.start(); expect(poll.state.phase).to.equal('started'); await poll.tick; expect(poll.state.phase).to.equal('resolved'); await poll.stop(); expect(poll.state.phase).to.equal('stopped'); await poll.start(); expect(poll.state.phase).to.equal('started'); await poll.tick; expect(poll.state.phase).to.equal('resolved'); expect(ticker.join(' ')).to.equal(expected); }); }); describe('#stop()', () => { it('should stop the poll if it is active', async () => { const expected = 'started stopped started resolved'; const ticker: IPoll.Phase[] = []; poll = new Poll({ auto: false, name: '@lumino/polling:Poll#stop()-1', frequency: { interval: 100 }, factory: () => Promise.resolve() }); poll.ticked.connect((_, tick) => { ticker.push(tick.phase); }); await poll.start(); expect(poll.state.phase).to.equal('started'); await poll.stop(); expect(poll.state.phase).to.equal('stopped'); await poll.start(); expect(poll.state.phase).to.equal('started'); await poll.tick; expect(poll.state.phase).to.equal('resolved'); expect(ticker.join(' ')).to.equal(expected); }); it('be safe to call multiple times', async () => { const expected = 'started stopped started resolved'; const ticker: IPoll.Phase[] = []; poll = new Poll({ auto: false, name: '@lumino/polling:Poll#stop()-2', frequency: { interval: 100 }, factory: () => Promise.resolve() }); poll.ticked.connect((_, tick) => { ticker.push(tick.phase); }); expect(poll.state.phase).to.equal('constructed'); await poll.start(); expect(poll.state.phase).to.equal('started'); await poll.stop(); expect(poll.state.phase).to.equal('stopped'); await poll.stop(); expect(poll.state.phase).to.equal('stopped'); await poll.stop(); expect(poll.state.phase).to.equal('stopped'); await poll.start(); expect(poll.state.phase).to.equal('started'); await poll.tick; expect(poll.state.phase).to.equal('resolved'); expect(ticker.join(' ')).to.equal(expected); }); }); describe('#schedule()', () => { it('should schedule the next poll state', async () => { poll = new Poll({ factory: () => Promise.resolve(), frequency: { interval: 100 }, name: '@lumino/polling:Poll#schedule()-1' }); expect(poll.state.phase).to.equal('constructed'); await poll.tick; expect(poll.state.phase).to.equal('started'); await poll.tick; expect(poll.state.phase).to.equal('resolved'); await poll.schedule({ phase: 'refreshed' }); expect(poll.state.phase).to.equal('refreshed'); return; }); it('should default to standby state', async () => { poll = new Poll({ factory: () => Promise.resolve(), frequency: { interval: 100 }, name: '@lumino/polling:Poll#schedule()-2' }); expect(poll.state.phase).to.equal('constructed'); await poll.tick; expect(poll.state.phase).to.equal('started'); await poll.tick; expect(poll.state.phase).to.equal('resolved'); await poll.schedule(); expect(poll.state.phase).to.equal('standby'); return; }); it('should support phase transition cancellation', async () => { poll = new Poll({ factory: () => Promise.resolve(), frequency: { interval: 100 }, name: '@lumino/polling:Poll#schedule()-3' }); expect(poll.state.phase).to.equal('constructed'); await poll.tick; expect(poll.state.phase).to.equal('started'); await poll.tick; expect(poll.state.phase).to.equal('resolved'); await poll.schedule(); expect(poll.state.phase).to.equal('standby'); await poll.schedule({ cancel: last => last.phase === 'standby', phase: 'refreshed' }); expect(poll.state.phase).to.equal('standby'); return; }); }); }); lumino-2021.12.13/packages/polling/tests/src/ratelimiter.spec.ts000066400000000000000000000077231415564225700244270ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. import { expect } from 'chai'; import { Debouncer, Throttler } from '@lumino/polling'; describe('Debouncer', () => { let debouncer: Debouncer; afterEach(() => { debouncer.dispose(); }); describe('#constructor()', () => { it('should create a debouncer', () => { debouncer = new Debouncer(async () => undefined); expect(debouncer).to.be.an.instanceof(Debouncer); }); }); describe('#invoke()', () => { it('should invoke and debounce a function', async () => { let counter = 0; debouncer = new Debouncer(() => counter++); expect(counter).to.equal(0); await debouncer.invoke(); expect(counter).to.equal(1); void debouncer.invoke(); void debouncer.invoke(); void debouncer.invoke(); await debouncer.invoke(); expect(counter).to.equal(2); }); }); }); describe('Throttler', () => { const limit = 500; let throttler: Throttler; afterEach(() => { throttler.dispose(); }); describe('#constructor()', () => { it('should create a debouncer', () => { throttler = new Throttler(async () => undefined); expect(throttler).to.be.an.instanceof(Throttler); }); }); describe('#invoke()', () => { it('should invoke and throttle a function', async () => { let counter = 0; throttler = new Throttler(() => counter++); expect(counter).to.equal(0); await throttler.invoke(); expect(counter).to.equal(1); void throttler.invoke(); void throttler.invoke(); void throttler.invoke(); await throttler.invoke(); expect(counter).to.equal(2); }); it('should collapse invocations into one promise per cycle', async () => { throttler = new Throttler(() => undefined, limit); const first = throttler.invoke(); const second = throttler.invoke(); const third = throttler.invoke(); const fourth = throttler.invoke(); const fifth = throttler.invoke(); await fifth; const sixth = throttler.invoke(); const seventh = throttler.invoke(); expect(first).to.equal(second, 'first === second'); expect(first).to.equal(third, 'first === third'); expect(first).to.equal(fourth, 'first === fourth'); expect(first).to.equal(fifth, 'first === fifth'); expect(fifth).not.to.equal(sixth, 'fifth !== sixth'); expect(sixth).to.equal(seventh, 'sixth === seventh'); }); it('should default to the `leading` edge of cycle', async () => { const started = new Date().getTime(); let invoked = 0; throttler = new Throttler(() => { invoked = new Date().getTime(); expect(invoked - started).to.be.lessThan(limit); }, limit); void throttler.invoke(); void throttler.invoke(); void throttler.invoke(); void throttler.invoke(); await throttler.invoke(); }); it('should support the `leading` edge of cycle', async () => { const edge = 'leading'; const started = new Date().getTime(); let invoked = 0; throttler = new Throttler( () => { invoked = new Date().getTime(); expect(invoked - started).to.be.lessThan(limit); }, { edge, limit } ); void throttler.invoke(); void throttler.invoke(); void throttler.invoke(); void throttler.invoke(); await throttler.invoke(); }); it('should support the `trailing` edge of cycle', async () => { const edge = 'trailing'; const started = new Date().getTime(); let invoked = 0; throttler = new Throttler( () => { invoked = new Date().getTime(); expect(invoked - started).to.be.gte(limit); }, { edge, limit } ); void throttler.invoke(); void throttler.invoke(); void throttler.invoke(); void throttler.invoke(); await throttler.invoke(); }); }); }); lumino-2021.12.13/packages/polling/tests/tsconfig.json000066400000000000000000000010421415564225700225110ustar00rootroot00000000000000{ "compilerOptions": { "composite": true, "declaration": true, "noImplicitAny": true, "noEmitOnError": true, "noUnusedLocals": true, "strictNullChecks": true, "module": "commonjs", "moduleResolution": "node", "target": "ES5", "outDir": "build", "lib": ["DOM", "ES5", "ES2015.Promise", "ES2015.Iterable"], "types": ["chai", "mocha"], "rootDir": "src" }, "include": ["src/*"], "references": [ { "path": "../../algorithm" }, { "path": "../../signaling" } ] } lumino-2021.12.13/packages/polling/tests/webpack.config.js000066400000000000000000000003061415564225700232220ustar00rootroot00000000000000var path = require('path'); module.exports = { entry: './build/index.spec.js', mode: 'development', output: { filename: './build/bundle.test.js', path: path.resolve(__dirname) } }; lumino-2021.12.13/packages/polling/tsconfig.json000066400000000000000000000012321415564225700213500ustar00rootroot00000000000000{ "compilerOptions": { "composite": true, "sourceMap": true, "declaration": true, "declarationDir": "./types", "declarationMap": true, "noImplicitAny": true, "noEmitOnError": true, "noUnusedLocals": true, "strictNullChecks": true, "module": "ES6", "moduleResolution": "node", "target": "ES5", "outDir": "lib", "lib": ["DOM", "ES5", "ES2015.Promise"], "importHelpers": true, "types": ["@types/node"], "rootDir": "src" }, "include": ["src/*"], "references": [ { "path": "../coreutils" }, { "path": "../disposable" }, { "path": "../signaling" } ] } lumino-2021.12.13/packages/properties/000077500000000000000000000000001415564225700173735ustar00rootroot00000000000000lumino-2021.12.13/packages/properties/api-extractor.json000066400000000000000000000014611415564225700230520ustar00rootroot00000000000000/** * Config file for API Extractor. For more info, please visit: https://api-extractor.com */ { "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", /** * Optionally specifies another JSON config file that this file extends from. This provides a way for * standard settings to be shared across multiple projects. * * If the path starts with "./" or "../", the path is resolved relative to the folder of the file that contains * the "extends" field. Otherwise, the first path segment is interpreted as an NPM package name, and will be * resolved using NodeJS require(). * * SUPPORTED TOKENS: none * DEFAULT VALUE: "" */ "extends": "../../api-extractor-base.json" // "extends": "my-package/include/api-extractor-base.json" } lumino-2021.12.13/packages/properties/package.json000066400000000000000000000047551415564225700216740ustar00rootroot00000000000000{ "name": "@lumino/properties", "version": "1.8.1", "description": "Lumino Attached Properties", "homepage": "https://github.com/jupyterlab/lumino", "bugs": { "url": "https://github.com/jupyterlab/lumino/issues" }, "repository": { "type": "git", "url": "https://github.com/jupyterlab/lumino.git" }, "license": "BSD-3-Clause", "author": "S. Chris Colbert ", "contributors": [ "A. Darian ", "Dave Willmer ", "S. Chris Colbert ", "Steven Silvester " ], "main": "dist/index.js", "jsdelivr": "dist/index.min.js", "unpkg": "dist/index.min.js", "module": "dist/index.es6", "types": "types/index.d.ts", "files": [ "dist/*", "src/*", "types/*" ], "scripts": { "api": "api-extractor run --local --verbose", "build": "npm run build:src && rollup -c", "build:src": "tsc --build", "build:test": "tsc --build tests && cd tests && webpack", "clean": "rimraf ./lib && rimraf *.tsbuildinfo && rimraf ./types && rimraf ./dist", "clean:test": "rimraf tests/build", "docs": "typedoc --options tdoptions.json src", "minimize": "terser dist/index.js -c -m --source-map \"content='dist/index.js.map',url='index.min.js.map'\" -o dist/index.min.js", "test": "npm run test:firefox-headless", "test:chrome": "cd tests && karma start --browsers=Chrome", "test:chrome-headless": "cd tests && karma start --browsers=ChromeHeadless", "test:firefox": "cd tests && karma start --browsers=Firefox", "test:firefox-headless": "cd tests && karma start --browsers=FirefoxHeadless", "test:ie": "cd tests && karma start --browsers=IE", "watch": "tsc --build --watch" }, "devDependencies": { "@microsoft/api-extractor": "^7.6.0", "@types/chai": "^3.4.35", "@types/mocha": "^2.2.39", "chai": "^4.3.4", "karma": "^6.3.4", "karma-chrome-launcher": "^3.1.0", "karma-firefox-launcher": "^2.1.1", "karma-ie-launcher": "^1.0.0", "karma-mocha": "^2.0.1", "karma-mocha-reporter": "^2.2.5", "mocha": "^9.0.3", "rimraf": "^3.0.2", "rollup": "^2.56.0", "rollup-plugin-node-resolve": "^5.2.0", "rollup-plugin-postcss": "^4.0.0", "rollup-plugin-sourcemaps": "^0.6.3", "terser": "^5.7.1", "tslib": "^2.3.0", "typedoc": "~0.15.0", "typescript": "~3.6.0", "webpack": "^4.41.3", "webpack-cli": "^3.3.10" }, "publishConfig": { "access": "public" } } lumino-2021.12.13/packages/properties/rollup.config.js000066400000000000000000000015231415564225700225130ustar00rootroot00000000000000import nodeResolve from 'rollup-plugin-node-resolve'; import sourcemaps from 'rollup-plugin-sourcemaps'; import postcss from 'rollup-plugin-postcss'; const pkg = require('./package.json'); const globals = id => id.indexOf('@lumino/') === 0 ? id.replace('@lumino/', 'lumino_') : id; export default [ { input: 'lib/index', external: id => pkg.dependencies && !!pkg.dependencies[id], output: [ { file: pkg.main, globals, format: 'umd', sourcemap: true, name: pkg.name }, { file: pkg.module + '.js', format: 'es', sourcemap: true, name: pkg.name } ], plugins: [ nodeResolve({ preferBuiltins: true }), sourcemaps(), postcss({ extensions: ['.css'], minimize: true }) ] } ]; lumino-2021.12.13/packages/properties/src/000077500000000000000000000000001415564225700201625ustar00rootroot00000000000000lumino-2021.12.13/packages/properties/src/index.ts000066400000000000000000000174621415564225700216530ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ /** * A class which attaches a value to an external object. * * #### Notes * Attached properties are used to extend the state of an object with * semantic data from an unrelated class. They also encapsulate value * creation, coercion, and notification. * * Because attached property values are stored in a hash table, which * in turn is stored in a WeakMap keyed on the owner object, there is * non-trivial storage overhead involved in their use. The pattern is * therefore best used for the storage of rare data. */ export class AttachedProperty { /** * Construct a new attached property. * * @param options - The options for initializing the property. */ constructor(options: AttachedProperty.IOptions) { this.name = options.name; this._create = options.create; this._coerce = options.coerce || null; this._compare = options.compare || null; this._changed = options.changed || null; } /** * The human readable name for the property. */ readonly name: string; /** * Get the current value of the property for a given owner. * * @param owner - The property owner of interest. * * @returns The current value of the property. * * #### Notes * If the value has not yet been set, the default value will be * computed and assigned as the current value of the property. */ get(owner: T): U { let value: U; let map = Private.ensureMap(owner); if (this._pid in map) { value = map[this._pid]; } else { value = map[this._pid] = this._createValue(owner); } return value; } /** * Set the current value of the property for a given owner. * * @param owner - The property owner of interest. * * @param value - The value for the property. * * #### Notes * If the value has not yet been set, the default value will be * computed and used as the previous value for the comparison. */ set(owner: T, value: U): void { let oldValue: U; let map = Private.ensureMap(owner); if (this._pid in map) { oldValue = map[this._pid]; } else { oldValue = map[this._pid] = this._createValue(owner); } let newValue = this._coerceValue(owner, value); this._maybeNotify(owner, oldValue, (map[this._pid] = newValue)); } /** * Explicitly coerce the current property value for a given owner. * * @param owner - The property owner of interest. * * #### Notes * If the value has not yet been set, the default value will be * computed and used as the previous value for the comparison. */ coerce(owner: T): void { let oldValue: U; let map = Private.ensureMap(owner); if (this._pid in map) { oldValue = map[this._pid]; } else { oldValue = map[this._pid] = this._createValue(owner); } let newValue = this._coerceValue(owner, oldValue); this._maybeNotify(owner, oldValue, (map[this._pid] = newValue)); } /** * Get or create the default value for the given owner. */ private _createValue(owner: T): U { let create = this._create; return create(owner); } /** * Coerce the value for the given owner. */ private _coerceValue(owner: T, value: U): U { let coerce = this._coerce; return coerce ? coerce(owner, value) : value; } /** * Compare the old value and new value for equality. */ private _compareValue(oldValue: U, newValue: U): boolean { let compare = this._compare; return compare ? compare(oldValue, newValue) : oldValue === newValue; } /** * Run the change notification if the given values are different. */ private _maybeNotify(owner: T, oldValue: U, newValue: U): void { let changed = this._changed; if (changed && !this._compareValue(oldValue, newValue)) { changed(owner, oldValue, newValue); } } private _pid = Private.nextPID(); private _create: (owner: T) => U; private _coerce: ((owner: T, value: U) => U) | null; private _compare: ((oldValue: U, newValue: U) => boolean) | null; private _changed: ((owner: T, oldValue: U, newValue: U) => void) | null; } /** * The namespace for the `AttachedProperty` class statics. */ export namespace AttachedProperty { /** * The options object used to initialize an attached property. */ export interface IOptions { /** * The human readable name for the property. * * #### Notes * By convention, this should be the same as the name used to define * the public accessor for the property value. * * This **does not** have an effect on the property lookup behavior. * Multiple properties may share the same name without conflict. */ name: string; /** * A factory function used to create the default property value. * * #### Notes * This will be called whenever the property value is required, * but has not yet been set for a given owner. */ create: (owner: T) => U; /** * A function used to coerce a supplied value into the final value. * * #### Notes * This will be called whenever the property value is changed, or * when the property is explicitly coerced. The return value will * be used as the final value of the property. * * This will **not** be called for the initial default value. */ coerce?: (owner: T, value: U) => U; /** * A function used to compare two values for equality. * * #### Notes * This is called to determine if the property value has changed. * It should return `true` if the given values are equivalent, or * `false` if they are different. * * If this is not provided, it defaults to the `===` operator. */ compare?: (oldValue: U, newValue: U) => boolean; /** * A function called when the property value has changed. * * #### Notes * This will be invoked when the property value is changed and the * comparator indicates that the old value is not equal to the new * value. * * This will **not** be called for the initial default value. */ changed?: (owner: T, oldValue: U, newValue: U) => void; } /** * Clear the stored property data for the given owner. * * @param owner - The property owner of interest. * * #### Notes * This will clear all property values for the owner, but it will * **not** run the change notification for any of the properties. */ export function clearData(owner: any): void { Private.ownerData.delete(owner); } } /** * The namespace for the module implementation details. */ namespace Private { /** * A typedef for a mapping of property id to property value. */ export type PropertyMap = { [key: string]: any }; /** * A weak mapping of property owner to property map. */ export const ownerData = new WeakMap(); /** * A function which computes successive unique property ids. */ export const nextPID = (() => { let id = 0; return () => { let rand = Math.random(); let stem = `${rand}`.slice(2); return `pid-${stem}-${id++}`; }; })(); /** * Lookup the data map for the property owner. * * This will create the map if one does not already exist. */ export function ensureMap(owner: any): PropertyMap { let map = ownerData.get(owner); if (map) { return map; } map = Object.create(null) as PropertyMap; ownerData.set(owner, map); return map; } } lumino-2021.12.13/packages/properties/tdoptions.json000066400000000000000000000005211415564225700223070ustar00rootroot00000000000000{ "excludeNotExported": true, "mode": "file", "target": "es5", "module": "es5", "lib": [ "lib.es2015.d.ts", "lib.es2015.collection.d.ts", "lib.es2015.promise.d.ts", "lib.dom.d.ts" ], "out": "../../docs/source/api/properties", "baseUrl": ".", "paths": { "@lumino/*": ["node_modules/@lumino/*"] } } lumino-2021.12.13/packages/properties/tests/000077500000000000000000000000001415564225700205355ustar00rootroot00000000000000lumino-2021.12.13/packages/properties/tests/karma.conf.js000066400000000000000000000005051415564225700231120ustar00rootroot00000000000000module.exports = function (config) { config.set({ basePath: '.', frameworks: ['mocha'], reporters: ['mocha'], files: ['build/bundle.test.js'], port: 9876, colors: true, singleRun: true, browserNoActivityTimeout: 30000, failOnEmptyTestSuite: false, logLevel: config.LOG_INFO }); }; lumino-2021.12.13/packages/properties/tests/src/000077500000000000000000000000001415564225700213245ustar00rootroot00000000000000lumino-2021.12.13/packages/properties/tests/src/index.spec.ts000066400000000000000000000350551415564225700237440ustar00rootroot00000000000000/* eslint-disable @typescript-eslint/no-empty-function */ // Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { expect } from 'chai'; import { AttachedProperty } from '@lumino/properties'; class Model { dummyValue = 42; } describe('@lumino/properties', () => { describe('AttachedProperty', () => { describe('#constructor()', () => { it('should accept a single options argument', () => { let p = new AttachedProperty({ name: 'p', create: owner => 42, coerce: (owner, value) => Math.max(0, value), compare: (oldValue, newValue) => oldValue === newValue, changed: (owner, oldValue, newValue) => {} }); expect(p).to.be.an.instanceof(AttachedProperty); }); }); describe('#name', () => { it('should be the name provided to the constructor', () => { let create = () => 0; let p = new AttachedProperty({ name: 'p', create }); expect(p.name).to.equal('p'); }); }); describe('#get()', () => { it('should return the current value of the property', () => { let tick = 42; let create = () => tick++; let p1 = new AttachedProperty({ name: 'p1', create }); let p2 = new AttachedProperty({ name: 'p2', create }); let p3 = new AttachedProperty({ name: 'p3', create }); let m1 = new Model(); let m2 = new Model(); let m3 = new Model(); expect(p1.get(m1)).to.equal(42); expect(p2.get(m1)).to.equal(43); expect(p3.get(m1)).to.equal(44); expect(p1.get(m2)).to.equal(45); expect(p2.get(m2)).to.equal(46); expect(p3.get(m2)).to.equal(47); expect(p1.get(m3)).to.equal(48); expect(p2.get(m3)).to.equal(49); expect(p3.get(m3)).to.equal(50); }); it('should not invoke the coerce function', () => { let called = false; let create = () => 0; let coerce = (m: Model, v: number) => ((called = true), v); let p1 = new AttachedProperty({ name: 'p1', create, coerce }); let p2 = new AttachedProperty({ name: 'p2', create, coerce }); let p3 = new AttachedProperty({ name: 'p3', create, coerce }); let m1 = new Model(); let m2 = new Model(); let m3 = new Model(); p1.get(m1); p2.get(m1); p3.get(m1); p1.get(m2); p2.get(m2); p3.get(m2); p1.get(m3); p2.get(m3); p3.get(m3); expect(called).to.equal(false); }); it('should not invoke the compare function', () => { let called = false; let create = () => 0; let compare = (v1: number, v2: number) => ((called = true), v1 === v2); let p1 = new AttachedProperty({ name: 'p1', create, compare }); let p2 = new AttachedProperty({ name: 'p2', create, compare }); let p3 = new AttachedProperty({ name: 'p3', create, compare }); let m1 = new Model(); let m2 = new Model(); let m3 = new Model(); p1.get(m1); p2.get(m1); p3.get(m1); p1.get(m2); p2.get(m2); p3.get(m2); p1.get(m3); p2.get(m3); p3.get(m3); expect(called).to.equal(false); }); it('should not invoke the changed function', () => { let called = false; let create = () => 0; let changed = () => { called = true; }; let p1 = new AttachedProperty({ name: 'p1', create, changed }); let p2 = new AttachedProperty({ name: 'p2', create, changed }); let p3 = new AttachedProperty({ name: 'p3', create, changed }); let m1 = new Model(); let m2 = new Model(); let m3 = new Model(); p1.get(m1); p2.get(m1); p3.get(m1); p1.get(m2); p2.get(m2); p3.get(m2); p1.get(m3); p2.get(m3); p3.get(m3); expect(called).to.equal(false); }); }); describe('#set()', () => { it('should set the current value of the property', () => { let create = () => 0; let p1 = new AttachedProperty({ name: 'p1', create }); let p2 = new AttachedProperty({ name: 'p2', create }); let p3 = new AttachedProperty({ name: 'p3', create }); let m1 = new Model(); let m2 = new Model(); let m3 = new Model(); p1.set(m1, 1); p1.set(m2, 2); p1.set(m3, 3); p2.set(m1, 4); p2.set(m2, 5); p2.set(m3, 6); p3.set(m1, 7); p3.set(m2, 8); p3.set(m3, 9); expect(p1.get(m1)).to.equal(1); expect(p1.get(m2)).to.equal(2); expect(p1.get(m3)).to.equal(3); expect(p2.get(m1)).to.equal(4); expect(p2.get(m2)).to.equal(5); expect(p2.get(m3)).to.equal(6); expect(p3.get(m1)).to.equal(7); expect(p3.get(m2)).to.equal(8); expect(p3.get(m3)).to.equal(9); }); it('should invoke the changed function if the value changes', () => { let oldvals: number[] = []; let newvals: number[] = []; let changed = (m: Model, o: number, n: number) => { oldvals.push(o); newvals.push(n); }; let create = () => 0; let p1 = new AttachedProperty({ name: 'p1', create, changed }); let p2 = new AttachedProperty({ name: 'p2', create, changed }); let p3 = new AttachedProperty({ name: 'p3', create, changed }); let m1 = new Model(); let m2 = new Model(); let m3 = new Model(); p1.set(m1, 1); p1.set(m2, 2); p1.set(m3, 3); p2.set(m1, 4); p2.set(m2, 5); p2.set(m3, 6); p3.set(m1, 7); p3.set(m2, 8); p3.set(m3, 9); expect(oldvals).to.deep.equal([0, 0, 0, 0, 0, 0, 0, 0, 0]); expect(newvals).to.deep.equal([1, 2, 3, 4, 5, 6, 7, 8, 9]); }); it('should invoke the coerce function on the new value', () => { let create = () => 0; let coerce = (o: Model, v: number) => Math.max(0, v); let p = new AttachedProperty({ name: 'p', create, coerce }); let m = new Model(); p.set(m, -10); expect(p.get(m)).to.equal(0); p.set(m, 10); expect(p.get(m)).to.equal(10); p.set(m, -42); expect(p.get(m)).to.equal(0); p.set(m, 42); expect(p.get(m)).to.equal(42); p.set(m, 0); expect(p.get(m)).to.equal(0); }); it('should not invoke the compare function if there is no changed function', () => { let called = false; let create = () => 0; let compare = (v1: number, v2: number) => ((called = true), v1 === v2); let p = new AttachedProperty({ name: 'p', create, compare }); let m = new Model(); p.set(m, 42); expect(called).to.equal(false); }); it('should invoke the compare function if there is a changed function', () => { let called = false; let create = () => 0; let changed = () => {}; let compare = (v1: number, v2: number) => ((called = true), v1 === v2); let p = new AttachedProperty({ name: 'p', create, compare, changed }); let m = new Model(); p.set(m, 42); expect(called).to.equal(true); }); it('should not invoke the changed function if the value does not change', () => { let called = false; let create = () => 1; let changed = () => { called = true; }; let compare = (v1: number, v2: number) => true; let p1 = new AttachedProperty({ name: 'p1', create, changed }); let p2 = new AttachedProperty({ name: 'p2', create, compare, changed }); let m = new Model(); p1.set(m, 1); p1.set(m, 1); p2.set(m, 1); p2.set(m, 2); p2.set(m, 3); p2.set(m, 4); expect(called).to.equal(false); }); }); describe('#coerce()', () => { it('should coerce the current value of the property', () => { let min = 20; let max = 50; let create = () => 0; let coerce = (m: Model, v: number) => Math.max(min, Math.min(v, max)); let p = new AttachedProperty({ name: 'p', create, coerce }); let m = new Model(); p.set(m, 10); expect(p.get(m)).to.equal(20); min = 30; p.coerce(m); expect(p.get(m)).to.equal(30); min = 10; max = 20; p.coerce(m); expect(p.get(m)).to.equal(20); }); it('should invoke the changed function if the value changes', () => { let called = false; let create = () => 0; let coerce = (m: Model, v: number) => Math.max(20, v); let changed = () => { called = true; }; let p = new AttachedProperty({ name: 'p', create, coerce, changed }); let m = new Model(); p.coerce(m); expect(called).to.equal(true); }); it('should use the default value as old value if value is not yet set', () => { let oldval = -1; let newval = -1; let create = () => 0; let coerce = (m: Model, v: number) => Math.max(20, v); let changed = (m: Model, o: number, n: number) => { oldval = o; newval = n; }; let p = new AttachedProperty({ name: 'p', create, coerce, changed }); let m = new Model(); p.coerce(m); expect(oldval).to.equal(0); expect(newval).to.equal(20); }); it('should not invoke the compare function if there is no changed function', () => { let called = false; let create = () => 0; let compare = (v1: number, v2: number) => ((called = true), v1 === v2); let p = new AttachedProperty({ name: 'p', create, compare }); let m = new Model(); p.coerce(m); expect(called).to.equal(false); }); it('should invoke the compare function if there is a changed function', () => { let called = false; let create = () => 0; let changed = () => {}; let compare = (v1: number, v2: number) => ((called = true), v1 === v2); let p = new AttachedProperty({ name: 'p', create, compare, changed }); let m = new Model(); p.coerce(m); expect(called).to.equal(true); }); it('should not invoke the changed function if the value does not change', () => { let called = false; let create = () => 0; let changed = () => { called = true; }; let p = new AttachedProperty({ name: 'p', create, changed }); let m = new Model(); p.coerce(m); expect(called).to.equal(false); }); }); describe('.clearData()', () => { it('should clear all property data for a property owner', () => { let create = () => 42; let p1 = new AttachedProperty({ name: 'p1', create }); let p2 = new AttachedProperty({ name: 'p2', create }); let p3 = new AttachedProperty({ name: 'p3', create }); let m1 = new Model(); let m2 = new Model(); let m3 = new Model(); p1.set(m1, 1); p1.set(m2, 2); p1.set(m3, 3); p2.set(m1, 4); p2.set(m2, 5); p2.set(m3, 6); p3.set(m1, 7); p3.set(m2, 8); p3.set(m3, 9); expect(p1.get(m1)).to.equal(1); expect(p1.get(m2)).to.equal(2); expect(p1.get(m3)).to.equal(3); expect(p2.get(m1)).to.equal(4); expect(p2.get(m2)).to.equal(5); expect(p2.get(m3)).to.equal(6); expect(p3.get(m1)).to.equal(7); expect(p3.get(m2)).to.equal(8); expect(p3.get(m3)).to.equal(9); AttachedProperty.clearData(m1); expect(p1.get(m1)).to.equal(42); expect(p1.get(m2)).to.equal(2); expect(p1.get(m3)).to.equal(3); expect(p2.get(m1)).to.equal(42); expect(p2.get(m2)).to.equal(5); expect(p2.get(m3)).to.equal(6); expect(p3.get(m1)).to.equal(42); expect(p3.get(m2)).to.equal(8); expect(p3.get(m3)).to.equal(9); AttachedProperty.clearData(m2); expect(p1.get(m1)).to.equal(42); expect(p1.get(m2)).to.equal(42); expect(p1.get(m3)).to.equal(3); expect(p2.get(m1)).to.equal(42); expect(p2.get(m2)).to.equal(42); expect(p2.get(m3)).to.equal(6); expect(p3.get(m1)).to.equal(42); expect(p3.get(m2)).to.equal(42); expect(p3.get(m3)).to.equal(9); AttachedProperty.clearData(m3); expect(p1.get(m1)).to.equal(42); expect(p1.get(m2)).to.equal(42); expect(p1.get(m3)).to.equal(42); expect(p2.get(m1)).to.equal(42); expect(p2.get(m2)).to.equal(42); expect(p2.get(m3)).to.equal(42); expect(p3.get(m1)).to.equal(42); expect(p3.get(m2)).to.equal(42); expect(p3.get(m3)).to.equal(42); }); }); }); }); lumino-2021.12.13/packages/properties/tests/tsconfig.json000066400000000000000000000005601415564225700232450ustar00rootroot00000000000000{ "compilerOptions": { "declaration": false, "noImplicitAny": true, "noEmitOnError": true, "noUnusedLocals": true, "strictNullChecks": true, "module": "commonjs", "moduleResolution": "node", "target": "ES5", "outDir": "build", "lib": ["ES5"], "types": ["chai", "mocha"], "rootDir": "src" }, "include": ["src/*"] } lumino-2021.12.13/packages/properties/tests/webpack.config.js000066400000000000000000000003061415564225700237520ustar00rootroot00000000000000var path = require('path'); module.exports = { entry: './build/index.spec.js', mode: 'development', output: { filename: './build/bundle.test.js', path: path.resolve(__dirname) } }; lumino-2021.12.13/packages/properties/tsconfig.json000066400000000000000000000010071415564225700221000ustar00rootroot00000000000000{ "compilerOptions": { "composite": true, "sourceMap": true, "declaration": true, "declarationDir": "./types", "declarationMap": true, "noImplicitAny": true, "noEmitOnError": true, "noUnusedLocals": true, "strictNullChecks": true, "module": "ES6", "moduleResolution": "node", "target": "ES5", "outDir": "lib", "lib": ["ES5", "ES2015.Collection", "ES2015.Iterable"], "importHelpers": true, "types": [], "rootDir": "src" }, "include": ["src/*"] } lumino-2021.12.13/packages/signaling/000077500000000000000000000000001415564225700171525ustar00rootroot00000000000000lumino-2021.12.13/packages/signaling/api-extractor.json000066400000000000000000000014611415564225700226310ustar00rootroot00000000000000/** * Config file for API Extractor. For more info, please visit: https://api-extractor.com */ { "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", /** * Optionally specifies another JSON config file that this file extends from. This provides a way for * standard settings to be shared across multiple projects. * * If the path starts with "./" or "../", the path is resolved relative to the folder of the file that contains * the "extends" field. Otherwise, the first path segment is interpreted as an NPM package name, and will be * resolved using NodeJS require(). * * SUPPORTED TOKENS: none * DEFAULT VALUE: "" */ "extends": "../../api-extractor-base.json" // "extends": "my-package/include/api-extractor-base.json" } lumino-2021.12.13/packages/signaling/package.json000066400000000000000000000050461415564225700214450ustar00rootroot00000000000000{ "name": "@lumino/signaling", "version": "1.10.1", "description": "Lumino Signals and Slots", "homepage": "https://github.com/jupyterlab/lumino", "bugs": { "url": "https://github.com/jupyterlab/lumino/issues" }, "repository": { "type": "git", "url": "https://github.com/jupyterlab/lumino.git" }, "license": "BSD-3-Clause", "author": "S. Chris Colbert ", "contributors": [ "A. Darian ", "Dave Willmer ", "S. Chris Colbert ", "Steven Silvester " ], "main": "dist/index.js", "jsdelivr": "dist/index.min.js", "unpkg": "dist/index.min.js", "module": "dist/index.es6", "types": "types/index.d.ts", "files": [ "dist/*", "src/*", "types/*" ], "scripts": { "api": "api-extractor run --local --verbose", "build": "npm run build:src && rollup -c", "build:src": "tsc --build", "build:test": "tsc --build tests && cd tests && webpack", "clean": "rimraf ./lib && rimraf *.tsbuildinfo && rimraf ./types && rimraf ./dist", "clean:test": "rimraf tests/build", "docs": "typedoc --options tdoptions.json src", "minimize": "terser dist/index.js -c -m --source-map \"content='dist/index.js.map',url='index.min.js.map'\" -o dist/index.min.js", "test": "npm run test:firefox-headless", "test:chrome": "cd tests && karma start --browsers=Chrome", "test:chrome-headless": "cd tests && karma start --browsers=ChromeHeadless", "test:firefox": "cd tests && karma start --browsers=Firefox", "test:firefox-headless": "cd tests && karma start --browsers=FirefoxHeadless", "test:ie": "cd tests && karma start --browsers=IE", "watch": "tsc --build --watch" }, "dependencies": { "@lumino/algorithm": "^1.9.1" }, "devDependencies": { "@microsoft/api-extractor": "^7.6.0", "@types/chai": "^3.4.35", "@types/mocha": "^2.2.39", "chai": "^4.3.4", "karma": "^6.3.4", "karma-chrome-launcher": "^3.1.0", "karma-firefox-launcher": "^2.1.1", "karma-ie-launcher": "^1.0.0", "karma-mocha": "^2.0.1", "karma-mocha-reporter": "^2.2.5", "mocha": "^9.0.3", "rimraf": "^3.0.2", "rollup": "^2.56.0", "rollup-plugin-node-resolve": "^5.2.0", "rollup-plugin-postcss": "^4.0.0", "rollup-plugin-sourcemaps": "^0.6.3", "terser": "^5.7.1", "tslib": "^2.3.0", "typedoc": "~0.15.0", "typescript": "~3.6.0", "webpack": "^4.41.3", "webpack-cli": "^3.3.10" }, "publishConfig": { "access": "public" } } lumino-2021.12.13/packages/signaling/rollup.config.js000066400000000000000000000015231415564225700222720ustar00rootroot00000000000000import nodeResolve from 'rollup-plugin-node-resolve'; import sourcemaps from 'rollup-plugin-sourcemaps'; import postcss from 'rollup-plugin-postcss'; const pkg = require('./package.json'); const globals = id => id.indexOf('@lumino/') === 0 ? id.replace('@lumino/', 'lumino_') : id; export default [ { input: 'lib/index', external: id => pkg.dependencies && !!pkg.dependencies[id], output: [ { file: pkg.main, globals, format: 'umd', sourcemap: true, name: pkg.name }, { file: pkg.module + '.js', format: 'es', sourcemap: true, name: pkg.name } ], plugins: [ nodeResolve({ preferBuiltins: true }), sourcemaps(), postcss({ extensions: ['.css'], minimize: true }) ] } ]; lumino-2021.12.13/packages/signaling/src/000077500000000000000000000000001415564225700177415ustar00rootroot00000000000000lumino-2021.12.13/packages/signaling/src/index.ts000066400000000000000000000440441415564225700214260ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { ArrayExt, each, find } from '@lumino/algorithm'; /** * A type alias for a slot function. * * @param sender - The object emitting the signal. * * @param args - The args object emitted with the signal. * * #### Notes * A slot is invoked when a signal to which it is connected is emitted. */ export type Slot = (sender: T, args: U) => void; /** * An object used for type-safe inter-object communication. * * #### Notes * Signals provide a type-safe implementation of the publish-subscribe * pattern. An object (publisher) declares which signals it will emit, * and consumers connect callbacks (subscribers) to those signals. The * subscribers are invoked whenever the publisher emits the signal. */ export interface ISignal { /** * Connect a slot to the signal. * * @param slot - The slot to invoke when the signal is emitted. * * @param thisArg - The `this` context for the slot. If provided, * this must be a non-primitive object. * * @returns `true` if the connection succeeds, `false` otherwise. * * #### Notes * Slots are invoked in the order in which they are connected. * * Signal connections are unique. If a connection already exists for * the given `slot` and `thisArg`, this method returns `false`. * * A newly connected slot will not be invoked until the next time the * signal is emitted, even if the slot is connected while the signal * is dispatching. */ connect(slot: Slot, thisArg?: any): boolean; /** * Disconnect a slot from the signal. * * @param slot - The slot to disconnect from the signal. * * @param thisArg - The `this` context for the slot. If provided, * this must be a non-primitive object. * * @returns `true` if the connection is removed, `false` otherwise. * * #### Notes * If no connection exists for the given `slot` and `thisArg`, this * method returns `false`. * * A disconnected slot will no longer be invoked, even if the slot * is disconnected while the signal is dispatching. */ disconnect(slot: Slot, thisArg?: any): boolean; } /** * A concrete implementation of `ISignal`. * * #### Example * ```typescript * import { ISignal, Signal } from '@lumino/signaling'; * * class SomeClass { * * constructor(name: string) { * this.name = name; * } * * readonly name: string; * * get valueChanged: ISignal { * return this._valueChanged; * } * * get value(): number { * return this._value; * } * * set value(value: number) { * if (value === this._value) { * return; * } * this._value = value; * this._valueChanged.emit(value); * } * * private _value = 0; * private _valueChanged = new Signal(this); * } * * function logger(sender: SomeClass, value: number): void { * console.log(sender.name, value); * } * * let m1 = new SomeClass('foo'); * let m2 = new SomeClass('bar'); * * m1.valueChanged.connect(logger); * m2.valueChanged.connect(logger); * * m1.value = 42; // logs: foo 42 * m2.value = 17; // logs: bar 17 * ``` */ export class Signal implements ISignal { /** * Construct a new signal. * * @param sender - The sender which owns the signal. */ constructor(sender: T) { this.sender = sender; } /** * The sender which owns the signal. */ readonly sender: T; /** * Connect a slot to the signal. * * @param slot - The slot to invoke when the signal is emitted. * * @param thisArg - The `this` context for the slot. If provided, * this must be a non-primitive object. * * @returns `true` if the connection succeeds, `false` otherwise. */ connect(slot: Slot, thisArg?: any): boolean { return Private.connect(this, slot, thisArg); } /** * Disconnect a slot from the signal. * * @param slot - The slot to disconnect from the signal. * * @param thisArg - The `this` context for the slot. If provided, * this must be a non-primitive object. * * @returns `true` if the connection is removed, `false` otherwise. */ disconnect(slot: Slot, thisArg?: any): boolean { return Private.disconnect(this, slot, thisArg); } /** * Emit the signal and invoke the connected slots. * * @param args - The args to pass to the connected slots. * * #### Notes * Slots are invoked synchronously in connection order. * * Exceptions thrown by connected slots will be caught and logged. */ emit(args: U): void { Private.emit(this, args); } } /** * The namespace for the `Signal` class statics. */ export namespace Signal { /** * Remove all connections between a sender and receiver. * * @param sender - The sender object of interest. * * @param receiver - The receiver object of interest. * * #### Notes * If a `thisArg` is provided when connecting a signal, that object * is considered the receiver. Otherwise, the `slot` is considered * the receiver. */ export function disconnectBetween(sender: any, receiver: any): void { Private.disconnectBetween(sender, receiver); } /** * Remove all connections where the given object is the sender. * * @param sender - The sender object of interest. */ export function disconnectSender(sender: any): void { Private.disconnectSender(sender); } /** * Remove all connections where the given object is the receiver. * * @param receiver - The receiver object of interest. * * #### Notes * If a `thisArg` is provided when connecting a signal, that object * is considered the receiver. Otherwise, the `slot` is considered * the receiver. */ export function disconnectReceiver(receiver: any): void { Private.disconnectReceiver(receiver); } /** * Remove all connections where an object is the sender or receiver. * * @param object - The object of interest. * * #### Notes * If a `thisArg` is provided when connecting a signal, that object * is considered the receiver. Otherwise, the `slot` is considered * the receiver. */ export function disconnectAll(object: any): void { Private.disconnectAll(object); } /** * Clear all signal data associated with the given object. * * @param object - The object for which the data should be cleared. * * #### Notes * This removes all signal connections and any other signal data * associated with the object. */ export function clearData(object: any): void { Private.disconnectAll(object); } /** * A type alias for the exception handler function. */ export type ExceptionHandler = (err: Error) => void; /** * Get the signal exception handler. * * @returns The current exception handler. * * #### Notes * The default exception handler is `console.error`. */ export function getExceptionHandler(): ExceptionHandler { return Private.exceptionHandler; } /** * Set the signal exception handler. * * @param handler - The function to use as the exception handler. * * @returns The old exception handler. * * #### Notes * The exception handler is invoked when a slot throws an exception. */ export function setExceptionHandler( handler: ExceptionHandler ): ExceptionHandler { let old = Private.exceptionHandler; Private.exceptionHandler = handler; return old; } } /** * The namespace for the module implementation details. */ namespace Private { /** * The signal exception handler function. */ export let exceptionHandler: Signal.ExceptionHandler = (err: Error) => { console.error(err); }; /** * Connect a slot to a signal. * * @param signal - The signal of interest. * * @param slot - The slot to invoke when the signal is emitted. * * @param thisArg - The `this` context for the slot. If provided, * this must be a non-primitive object. * * @returns `true` if the connection succeeds, `false` otherwise. */ export function connect( signal: Signal, slot: Slot, thisArg?: any ): boolean { // Coerce a `null` `thisArg` to `undefined`. thisArg = thisArg || undefined; // Ensure the sender's array of receivers is created. let receivers = receiversForSender.get(signal.sender); if (!receivers) { receivers = []; receiversForSender.set(signal.sender, receivers); } // Bail if a matching connection already exists. if (findConnection(receivers, signal, slot, thisArg)) { return false; } // Choose the best object for the receiver. let receiver = thisArg || slot; // Ensure the receiver's array of senders is created. let senders = sendersForReceiver.get(receiver); if (!senders) { senders = []; sendersForReceiver.set(receiver, senders); } // Create a new connection and add it to the end of each array. let connection = { signal, slot, thisArg }; receivers.push(connection); senders.push(connection); // Indicate a successful connection. return true; } /** * Disconnect a slot from a signal. * * @param signal - The signal of interest. * * @param slot - The slot to disconnect from the signal. * * @param thisArg - The `this` context for the slot. If provided, * this must be a non-primitive object. * * @returns `true` if the connection is removed, `false` otherwise. */ export function disconnect( signal: Signal, slot: Slot, thisArg?: any ): boolean { // Coerce a `null` `thisArg` to `undefined`. thisArg = thisArg || undefined; // Lookup the list of receivers, and bail if none exist. let receivers = receiversForSender.get(signal.sender); if (!receivers || receivers.length === 0) { return false; } // Bail if no matching connection exits. let connection = findConnection(receivers, signal, slot, thisArg); if (!connection) { return false; } // Choose the best object for the receiver. let receiver = thisArg || slot; // Lookup the array of senders, which is now known to exist. let senders = sendersForReceiver.get(receiver)!; // Clear the connection and schedule cleanup of the arrays. connection.signal = null; scheduleCleanup(receivers); scheduleCleanup(senders); // Indicate a successful disconnection. return true; } /** * Remove all connections between a sender and receiver. * * @param sender - The sender object of interest. * * @param receiver - The receiver object of interest. */ export function disconnectBetween(sender: any, receiver: any): void { // If there are no receivers, there is nothing to do. let receivers = receiversForSender.get(sender); if (!receivers || receivers.length === 0) { return; } // If there are no senders, there is nothing to do. let senders = sendersForReceiver.get(receiver); if (!senders || senders.length === 0) { return; } // Clear each connection between the sender and receiver. each(senders, connection => { // Skip connections which have already been cleared. if (!connection.signal) { return; } // Clear the connection if it matches the sender. if (connection.signal.sender === sender) { connection.signal = null; } }); // Schedule a cleanup of the senders and receivers. scheduleCleanup(receivers); scheduleCleanup(senders); } /** * Remove all connections where the given object is the sender. * * @param sender - The sender object of interest. */ export function disconnectSender(sender: any): void { // If there are no receivers, there is nothing to do. let receivers = receiversForSender.get(sender); if (!receivers || receivers.length === 0) { return; } // Clear each receiver connection. each(receivers, connection => { // Skip connections which have already been cleared. if (!connection.signal) { return; } // Choose the best object for the receiver. let receiver = connection.thisArg || connection.slot; // Clear the connection. connection.signal = null; // Cleanup the array of senders, which is now known to exist. scheduleCleanup(sendersForReceiver.get(receiver)!); }); // Schedule a cleanup of the receivers. scheduleCleanup(receivers); } /** * Remove all connections where the given object is the receiver. * * @param receiver - The receiver object of interest. */ export function disconnectReceiver(receiver: any): void { // If there are no senders, there is nothing to do. let senders = sendersForReceiver.get(receiver); if (!senders || senders.length === 0) { return; } // Clear each sender connection. each(senders, connection => { // Skip connections which have already been cleared. if (!connection.signal) { return; } // Lookup the sender for the connection. let sender = connection.signal.sender; // Clear the connection. connection.signal = null; // Cleanup the array of receivers, which is now known to exist. scheduleCleanup(receiversForSender.get(sender)!); }); // Schedule a cleanup of the list of senders. scheduleCleanup(senders); } /** * Remove all connections where an object is the sender or receiver. * * @param object - The object of interest. */ export function disconnectAll(object: any): void { // Remove all connections where the given object is the sender. disconnectSender(object); // Remove all connections where the given object is the receiver. disconnectReceiver(object); } /** * Emit a signal and invoke its connected slots. * * @param signal - The signal of interest. * * @param args - The args to pass to the connected slots. * * #### Notes * Slots are invoked synchronously in connection order. * * Exceptions thrown by connected slots will be caught and logged. */ export function emit(signal: Signal, args: U): void { // If there are no receivers, there is nothing to do. let receivers = receiversForSender.get(signal.sender); if (!receivers || receivers.length === 0) { return; } // Invoke the slots for connections with a matching signal. // Any connections added during emission are not invoked. for (let i = 0, n = receivers.length; i < n; ++i) { let connection = receivers[i]; if (connection.signal === signal) { invokeSlot(connection, args); } } } /** * An object which holds connection data. */ interface IConnection { /** * The signal for the connection. * * A `null` signal indicates a cleared connection. */ signal: Signal | null; /** * The slot connected to the signal. */ readonly slot: Slot; /** * The `this` context for the slot. */ readonly thisArg: any; } /** * A weak mapping of sender to array of receiver connections. */ const receiversForSender = new WeakMap(); /** * A weak mapping of receiver to array of sender connections. */ const sendersForReceiver = new WeakMap(); /** * A set of connection arrays which are pending cleanup. */ const dirtySet = new Set(); /** * A function to schedule an event loop callback. */ const schedule = (() => { let ok = typeof requestAnimationFrame === 'function'; // @ts-ignore return ok ? requestAnimationFrame : setImmediate; })(); /** * Find a connection which matches the given parameters. */ function findConnection( connections: IConnection[], signal: Signal, slot: Slot, thisArg: any ): IConnection | undefined { return find( connections, connection => connection.signal === signal && connection.slot === slot && connection.thisArg === thisArg ); } /** * Invoke a slot with the given parameters. * * The connection is assumed to be valid. * * Exceptions in the slot will be caught and logged. */ function invokeSlot(connection: IConnection, args: any): void { let { signal, slot, thisArg } = connection; try { slot.call(thisArg, signal!.sender, args); } catch (err) { exceptionHandler(err); } } /** * Schedule a cleanup of a connection array. * * This will add the array to the dirty set and schedule a deferred * cleanup of the array contents. On cleanup, any connection with a * `null` signal will be removed from the array. */ function scheduleCleanup(array: IConnection[]): void { if (dirtySet.size === 0) { schedule(cleanupDirtySet); } dirtySet.add(array); } /** * Cleanup the connection lists in the dirty set. * * This function should only be invoked asynchronously, when the * stack frame is guaranteed to not be on the path of user code. */ function cleanupDirtySet(): void { dirtySet.forEach(cleanupConnections); dirtySet.clear(); } /** * Cleanup the dirty connections in a connections array. * * This will remove any connection with a `null` signal. * * This function should only be invoked asynchronously, when the * stack frame is guaranteed to not be on the path of user code. */ function cleanupConnections(connections: IConnection[]): void { ArrayExt.removeAllWhere(connections, isDeadConnection); } /** * Test whether a connection is dead. * * A dead connection has a `null` signal. */ function isDeadConnection(connection: IConnection): boolean { return connection.signal === null; } } lumino-2021.12.13/packages/signaling/tdoptions.json000066400000000000000000000005201415564225700220650ustar00rootroot00000000000000{ "excludeNotExported": true, "mode": "file", "target": "es5", "module": "es5", "lib": [ "lib.es2015.d.ts", "lib.es2015.collection.d.ts", "lib.es2015.promise.d.ts", "lib.dom.d.ts" ], "out": "../../docs/source/api/signaling", "baseUrl": ".", "paths": { "@lumino/*": ["node_modules/@lumino/*"] } } lumino-2021.12.13/packages/signaling/tests/000077500000000000000000000000001415564225700203145ustar00rootroot00000000000000lumino-2021.12.13/packages/signaling/tests/karma.conf.js000066400000000000000000000005051415564225700226710ustar00rootroot00000000000000module.exports = function (config) { config.set({ basePath: '.', frameworks: ['mocha'], reporters: ['mocha'], files: ['build/bundle.test.js'], port: 9876, colors: true, singleRun: true, browserNoActivityTimeout: 30000, failOnEmptyTestSuite: false, logLevel: config.LOG_INFO }); }; lumino-2021.12.13/packages/signaling/tests/src/000077500000000000000000000000001415564225700211035ustar00rootroot00000000000000lumino-2021.12.13/packages/signaling/tests/src/index.spec.ts000066400000000000000000000376731415564225700235330ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { expect } from 'chai'; import { Signal } from '@lumino/signaling'; class TestObject { readonly one = new Signal(this); readonly two = new Signal(this); readonly three = new Signal(this); } class ExtendedObject extends TestObject { notifyCount = 0; onNotify(): void { this.notifyCount++; } } class TestHandler { name = ''; oneCount = 0; twoValue = 0; twoSender: TestObject | null = null; onOne(): void { this.oneCount++; } onTwo(sender: TestObject, args: number): void { this.twoSender = sender; this.twoValue = args; } onThree(sender: TestObject, args: string[]): void { args.push(this.name); } onThrow(): void { throw new Error(); } } describe('@lumino/signaling', () => { describe('Signal', () => { describe('#sender', () => { it('should be the sender of the signal', () => { let obj = new TestObject(); expect(obj.one.sender).to.equal(obj); expect(obj.two.sender).to.equal(obj); expect(obj.three.sender).to.equal(obj); }); }); describe('#connect()', () => { it('should return true on success', () => { let obj = new TestObject(); let handler = new TestHandler(); let c1 = obj.one.connect(handler.onOne, handler); expect(c1).to.equal(true); }); it('should return false on failure', () => { let obj = new TestObject(); let handler = new TestHandler(); let c1 = obj.one.connect(handler.onOne, handler); let c2 = obj.one.connect(handler.onOne, handler); expect(c1).to.equal(true); expect(c2).to.equal(false); }); it('should connect plain functions', () => { let obj = new TestObject(); let handler = new TestHandler(); let c1 = obj.one.connect(handler.onThrow); expect(c1).to.equal(true); }); it('should ignore duplicate connections', () => { let obj = new TestObject(); let handler = new TestHandler(); let c1 = obj.one.connect(handler.onOne, handler); let c2 = obj.one.connect(handler.onOne, handler); let c3 = obj.two.connect(handler.onTwo, handler); let c4 = obj.two.connect(handler.onTwo, handler); obj.one.emit(undefined); obj.two.emit(42); expect(c1).to.equal(true); expect(c2).to.equal(false); expect(c3).to.equal(true); expect(c4).to.equal(false); expect(handler.oneCount).to.equal(1); expect(handler.twoValue).to.equal(42); }); it('should handle connect after disconnect and emit', () => { let obj = new TestObject(); let handler = new TestHandler(); let c1 = obj.one.connect(handler.onOne, handler); expect(c1).to.equal(true); obj.one.disconnect(handler.onOne, handler); obj.one.emit(undefined); let c2 = obj.one.connect(handler.onOne, handler); expect(c2).to.equal(true); }); }); describe('#disconnect()', () => { it('should return true on success', () => { let obj = new TestObject(); let handler = new TestHandler(); obj.one.connect(handler.onOne, handler); let d1 = obj.one.disconnect(handler.onOne, handler); expect(d1).to.equal(true); }); it('should return false on failure', () => { let obj = new TestObject(); let handler = new TestHandler(); let d1 = obj.one.disconnect(handler.onOne, handler); expect(d1).to.equal(false); }); it('should disconnect plain functions', () => { let obj = new TestObject(); let handler = new TestHandler(); obj.one.connect(handler.onThrow); expect(obj.one.disconnect(handler.onThrow)).to.equal(true); expect(() => obj.one.emit(undefined)).to.not.throw(Error); }); it('should disconnect a specific signal', () => { let obj1 = new TestObject(); let obj2 = new TestObject(); let obj3 = new TestObject(); let handler1 = new TestHandler(); let handler2 = new TestHandler(); let handler3 = new TestHandler(); obj1.one.connect(handler1.onOne, handler1); obj2.one.connect(handler2.onOne, handler2); obj1.one.connect(handler3.onOne, handler3); obj2.one.connect(handler3.onOne, handler3); obj3.one.connect(handler3.onOne, handler3); let d1 = obj1.one.disconnect(handler1.onOne, handler1); let d2 = obj1.one.disconnect(handler1.onOne, handler1); let d3 = obj2.one.disconnect(handler3.onOne, handler3); obj1.one.emit(undefined); obj2.one.emit(undefined); obj3.one.emit(undefined); expect(d1).to.equal(true); expect(d2).to.equal(false); expect(d3).to.equal(true); expect(handler1.oneCount).to.equal(0); expect(handler2.oneCount).to.equal(1); expect(handler3.oneCount).to.equal(2); }); it('should handle disconnecting sender after receiver', () => { let obj = new TestObject(); let handler = new TestHandler(); obj.one.connect(handler.onOne, handler); Signal.disconnectReceiver(handler); Signal.disconnectSender(obj); obj.one.emit(undefined); expect(handler.oneCount).to.equal(0); }); it('should handle disconnecting receiver after sender', () => { let obj = new TestObject(); let handler = new TestHandler(); obj.one.connect(handler.onOne, handler); Signal.disconnectSender(obj); Signal.disconnectReceiver(handler); obj.one.emit(undefined); expect(handler.oneCount).to.equal(0); }); }); describe('#emit()', () => { it('should be a no-op if there are no connection', () => { let obj = new TestObject(); expect(() => { obj.one.emit(undefined); }).to.not.throw(Error); }); it('should pass the sender and args to the handlers', () => { let obj = new TestObject(); let handler1 = new TestHandler(); let handler2 = new TestHandler(); obj.two.connect(handler1.onTwo, handler1); obj.two.connect(handler2.onTwo, handler2); obj.two.emit(15); expect(handler1.twoSender).to.equal(obj); expect(handler2.twoSender).to.equal(obj); expect(handler1.twoValue).to.equal(15); expect(handler2.twoValue).to.equal(15); }); it('should invoke handlers in connection order', () => { let obj = new TestObject(); let handler1 = new TestHandler(); let handler2 = new TestHandler(); let handler3 = new TestHandler(); handler1.name = 'foo'; handler2.name = 'bar'; handler3.name = 'baz'; obj.three.connect(handler1.onThree, handler1); obj.one.connect(handler1.onOne, handler1); obj.three.connect(handler2.onThree, handler2); obj.three.connect(handler3.onThree, handler3); let names: string[] = []; obj.three.emit(names); obj.one.emit(undefined); expect(names).to.deep.equal(['foo', 'bar', 'baz']); expect(handler1.oneCount).to.equal(1); expect(handler2.oneCount).to.equal(0); }); it('should catch any exceptions in handlers', () => { let obj = new TestObject(); let handler1 = new TestHandler(); let handler2 = new TestHandler(); let handler3 = new TestHandler(); handler1.name = 'foo'; handler2.name = 'bar'; handler3.name = 'baz'; obj.three.connect(handler1.onThree, handler1); obj.three.connect(handler2.onThrow, handler2); obj.three.connect(handler3.onThree, handler3); let threw = false; let names1: string[] = []; try { obj.three.emit(names1); } catch (e) { threw = true; } expect(threw).to.equal(false); expect(names1).to.deep.equal(['foo', 'baz']); }); it('should not invoke signals added during emission', () => { let obj = new TestObject(); let handler1 = new TestHandler(); let handler2 = new TestHandler(); let handler3 = new TestHandler(); handler1.name = 'foo'; handler2.name = 'bar'; handler3.name = 'baz'; let adder = { add: () => { obj.three.connect(handler3.onThree, handler3); } }; obj.three.connect(handler1.onThree, handler1); obj.three.connect(handler2.onThree, handler2); obj.three.connect(adder.add, adder); let names1: string[] = []; obj.three.emit(names1); obj.three.disconnect(adder.add, adder); let names2: string[] = []; obj.three.emit(names2); expect(names1).to.deep.equal(['foo', 'bar']); expect(names2).to.deep.equal(['foo', 'bar', 'baz']); }); it('should not invoke signals removed during emission', () => { let obj = new TestObject(); let handler1 = new TestHandler(); let handler2 = new TestHandler(); let handler3 = new TestHandler(); handler1.name = 'foo'; handler2.name = 'bar'; handler3.name = 'baz'; let remover = { remove: () => { obj.three.disconnect(handler3.onThree, handler3); } }; obj.three.connect(handler1.onThree, handler1); obj.three.connect(handler2.onThree, handler2); obj.three.connect(remover.remove, remover); obj.three.connect(handler3.onThree, handler3); let names: string[] = []; obj.three.emit(names); expect(names).to.deep.equal(['foo', 'bar']); }); }); describe('.disconnectBetween()', () => { it('should clear all connections between a sender and receiver', () => { let obj = new TestObject(); let handler1 = new TestHandler(); let handler2 = new TestHandler(); obj.one.connect(handler1.onOne, handler1); obj.one.connect(handler2.onOne, handler2); obj.two.connect(handler1.onTwo, handler1); obj.two.connect(handler2.onTwo, handler2); obj.one.emit(undefined); expect(handler1.oneCount).to.equal(1); expect(handler2.oneCount).to.equal(1); obj.two.emit(42); expect(handler1.twoValue).to.equal(42); expect(handler2.twoValue).to.equal(42); Signal.disconnectBetween(obj, handler1); obj.one.emit(undefined); expect(handler1.oneCount).to.equal(1); expect(handler2.oneCount).to.equal(2); obj.two.emit(7); expect(handler1.twoValue).to.equal(42); expect(handler2.twoValue).to.equal(7); }); it('should be a no-op if the sender or receiver is not connected', () => { expect(() => Signal.disconnectBetween({}, {})).to.not.throw(Error); }); }); describe('.disconnectSender()', () => { it('should disconnect all signals from a specific sender', () => { let obj1 = new TestObject(); let obj2 = new TestObject(); let handler1 = new TestHandler(); let handler2 = new TestHandler(); obj1.one.connect(handler1.onOne, handler1); obj1.one.connect(handler2.onOne, handler2); obj2.one.connect(handler1.onOne, handler1); obj2.one.connect(handler2.onOne, handler2); Signal.disconnectSender(obj1); obj1.one.emit(undefined); obj2.one.emit(undefined); expect(handler1.oneCount).to.equal(1); expect(handler2.oneCount).to.equal(1); }); it('should be a no-op if the sender is not connected', () => { expect(() => Signal.disconnectSender({})).to.not.throw(Error); }); }); describe('.disconnectReceiver()', () => { it('should disconnect all signals from a specific receiver', () => { let obj1 = new TestObject(); let obj2 = new TestObject(); let handler1 = new TestHandler(); let handler2 = new TestHandler(); obj1.one.connect(handler1.onOne, handler1); obj1.one.connect(handler2.onOne, handler2); obj2.one.connect(handler1.onOne, handler1); obj2.one.connect(handler2.onOne, handler2); obj2.two.connect(handler1.onTwo, handler1); obj2.two.connect(handler2.onTwo, handler2); Signal.disconnectReceiver(handler1); obj1.one.emit(undefined); obj2.one.emit(undefined); obj2.two.emit(42); expect(handler1.oneCount).to.equal(0); expect(handler2.oneCount).to.equal(2); expect(handler1.twoValue).to.equal(0); expect(handler2.twoValue).to.equal(42); }); it('should be a no-op if the receiver is not connected', () => { expect(() => Signal.disconnectReceiver({})).to.not.throw(Error); }); }); describe('.disconnectAll()', () => { it('should clear all connections for an object', () => { let counter = 0; let onCount = () => { counter++; }; let ext1 = new ExtendedObject(); let ext2 = new ExtendedObject(); ext1.one.connect(ext1.onNotify, ext1); ext1.one.connect(ext2.onNotify, ext2); ext1.one.connect(onCount); ext2.one.connect(ext1.onNotify, ext1); ext2.one.connect(ext2.onNotify, ext2); ext2.one.connect(onCount); Signal.disconnectAll(ext1); ext1.one.emit(undefined); ext2.one.emit(undefined); expect(ext1.notifyCount).to.equal(0); expect(ext2.notifyCount).to.equal(1); expect(counter).to.equal(1); }); }); describe('.clearData()', () => { it('should clear all signal data associated with an object', () => { let counter = 0; let onCount = () => { counter++; }; let ext1 = new ExtendedObject(); let ext2 = new ExtendedObject(); ext1.one.connect(ext1.onNotify, ext1); ext1.one.connect(ext2.onNotify, ext2); ext1.one.connect(onCount); ext2.one.connect(ext1.onNotify, ext1); ext2.one.connect(ext2.onNotify, ext2); ext2.one.connect(onCount); Signal.clearData(ext1); ext1.one.emit(undefined); ext2.one.emit(undefined); expect(ext1.notifyCount).to.equal(0); expect(ext2.notifyCount).to.equal(1); expect(counter).to.equal(1); }); }); describe('.getExceptionHandler()', () => { it('should default to an exception handler', () => { expect(Signal.getExceptionHandler()).to.be.a('function'); }); }); describe('.setExceptionHandler()', () => { afterEach(() => { Signal.setExceptionHandler(console.error); }); it('should set the exception handler', () => { let handler = (err: Error) => { console.error(err); }; Signal.setExceptionHandler(handler); expect(Signal.getExceptionHandler()).to.equal(handler); }); it('should return the old exception handler', () => { let handler = (err: Error) => { console.error(err); }; let old1 = Signal.setExceptionHandler(handler); let old2 = Signal.setExceptionHandler(old1); expect(old1).to.equal(console.error); expect(old2).to.equal(handler); }); it('should invoke the exception handler on a slot exception', () => { let called = false; let obj = new TestObject(); let handler = new TestHandler(); obj.one.connect(handler.onThrow, handler); Signal.setExceptionHandler(() => { called = true; }); expect(called).to.equal(false); obj.one.emit(undefined); expect(called).to.equal(true); }); }); }); }); lumino-2021.12.13/packages/signaling/tests/tsconfig.json000066400000000000000000000006721415564225700230300ustar00rootroot00000000000000{ "compilerOptions": { "declaration": false, "noImplicitAny": true, "noEmitOnError": true, "noUnusedLocals": true, "strictNullChecks": true, "module": "commonjs", "moduleResolution": "node", "target": "ES5", "outDir": "build", "lib": ["ES5", "DOM"], "types": ["chai", "mocha"], "rootDir": "src" }, "include": ["src/*"], "references": [ { "path": "../../algorithm" } ] } lumino-2021.12.13/packages/signaling/tests/webpack.config.js000066400000000000000000000003061415564225700235310ustar00rootroot00000000000000var path = require('path'); module.exports = { entry: './build/index.spec.js', mode: 'development', output: { filename: './build/bundle.test.js', path: path.resolve(__dirname) } }; lumino-2021.12.13/packages/signaling/tsconfig.json000066400000000000000000000010731415564225700216620ustar00rootroot00000000000000{ "compilerOptions": { "composite": true, "sourceMap": true, "declaration": true, "declarationDir": "./types", "declarationMap": true, "noImplicitAny": true, "noEmitOnError": true, "noUnusedLocals": true, "strictNullChecks": true, "module": "ES6", "moduleResolution": "node", "target": "ES5", "outDir": "lib", "lib": ["ES5", "ES2015.Collection", "DOM"], "importHelpers": true, "types": [], "rootDir": "src" }, "include": ["src/*"], "references": [ { "path": "../algorithm" } ] } lumino-2021.12.13/packages/virtualdom/000077500000000000000000000000001415564225700173655ustar00rootroot00000000000000lumino-2021.12.13/packages/virtualdom/api-extractor.json000066400000000000000000000014611415564225700230440ustar00rootroot00000000000000/** * Config file for API Extractor. For more info, please visit: https://api-extractor.com */ { "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", /** * Optionally specifies another JSON config file that this file extends from. This provides a way for * standard settings to be shared across multiple projects. * * If the path starts with "./" or "../", the path is resolved relative to the folder of the file that contains * the "extends" field. Otherwise, the first path segment is interpreted as an NPM package name, and will be * resolved using NodeJS require(). * * SUPPORTED TOKENS: none * DEFAULT VALUE: "" */ "extends": "../../api-extractor-base.json" // "extends": "my-package/include/api-extractor-base.json" } lumino-2021.12.13/packages/virtualdom/package.json000066400000000000000000000062101415564225700216520ustar00rootroot00000000000000{ "name": "@lumino/virtualdom", "version": "1.14.1", "description": "Lumino Virtual DOM", "homepage": "https://github.com/jupyterlab/lumino", "bugs": { "url": "https://github.com/jupyterlab/lumino/issues" }, "repository": { "type": "git", "url": "https://github.com/jupyterlab/lumino.git" }, "license": "BSD-3-Clause", "author": "S. Chris Colbert ", "contributors": [ "A. Darian ", "Dave Willmer ", "S. Chris Colbert ", "Steven Silvester " ], "main": "dist/index.js", "jsdelivr": "dist/index.min.js", "unpkg": "dist/index.min.js", "module": "dist/index.es6", "types": "types/index.d.ts", "files": [ "dist/*", "src/*", "types/*" ], "scripts": { "api": "api-extractor run --local --verbose", "build": "npm run build:src && rollup -c", "build:src": "tsc --build", "build:test": "tsc --build tests && cd tests && webpack", "clean": "rimraf ./lib && rimraf *.tsbuildinfo && rimraf ./types && rimraf ./dist", "clean:test": "rimraf tests/build", "docs": "typedoc --options tdoptions.json src", "minimize": "terser dist/index.js -c -m --source-map \"content='dist/index.js.map',url='index.min.js.map'\" -o dist/index.min.js", "test": "npm run test:firefox-headless", "test:chrome": "npm run test:nobrowser -- --browsers=Chrome", "test:chrome-headless": "npm run test:nobrowser -- --browsers=ChromeHeadless", "test:debug": "npm run test:debug:firefox", "test:debug:chrome": "npm run test:debug:nobrowser -- --browsers=Chrome", "test:debug:chrome-headless": "npm run test:debug:nobrowser -- --browsers=ChromeHeadless", "test:debug:firefox": "npm run test:debug:nobrowser -- --browsers=Firefox", "test:debug:firefox-headless": "npm run test:debug:nobrowser -- --browsers=FirefoxHeadless", "test:debug:nobrowser": "cd tests && karma start --singleRun=false --debug=true --browserNoActivityTimeout=10000000 --browserDisconnectTimeout=10000000", "test:firefox": "npm run test:nobrowser -- --browsers=Firefox", "test:firefox-headless": "npm run test:nobrowser -- --browsers=FirefoxHeadless", "test:ie": "npm run test:nobrowser -- --browsers=IE", "test:nobrowser": "cd tests && karma start", "watch": "tsc --build --watch" }, "dependencies": { "@lumino/algorithm": "^1.9.1" }, "devDependencies": { "@microsoft/api-extractor": "^7.6.0", "@types/chai": "^3.4.35", "@types/mocha": "^2.2.39", "chai": "^4.3.4", "karma": "^6.3.4", "karma-chrome-launcher": "^3.1.0", "karma-firefox-launcher": "^2.1.1", "karma-ie-launcher": "^1.0.0", "karma-mocha": "^2.0.1", "karma-mocha-reporter": "^2.2.5", "mocha": "^9.0.3", "rimraf": "^3.0.2", "rollup": "^2.56.0", "rollup-plugin-node-resolve": "^5.2.0", "rollup-plugin-postcss": "^4.0.0", "rollup-plugin-sourcemaps": "^0.6.3", "terser": "^5.7.1", "tslib": "^2.3.0", "typedoc": "~0.15.0", "typescript": "~3.6.0", "webpack": "^4.41.3", "webpack-cli": "^3.3.10" }, "publishConfig": { "access": "public" } } lumino-2021.12.13/packages/virtualdom/rollup.config.js000066400000000000000000000015231415564225700225050ustar00rootroot00000000000000import nodeResolve from 'rollup-plugin-node-resolve'; import sourcemaps from 'rollup-plugin-sourcemaps'; import postcss from 'rollup-plugin-postcss'; const pkg = require('./package.json'); const globals = id => id.indexOf('@lumino/') === 0 ? id.replace('@lumino/', 'lumino_') : id; export default [ { input: 'lib/index', external: id => pkg.dependencies && !!pkg.dependencies[id], output: [ { file: pkg.main, globals, format: 'umd', sourcemap: true, name: pkg.name }, { file: pkg.module + '.js', format: 'es', sourcemap: true, name: pkg.name } ], plugins: [ nodeResolve({ preferBuiltins: true }), sourcemaps(), postcss({ extensions: ['.css'], minimize: true }) ] } ]; lumino-2021.12.13/packages/virtualdom/src/000077500000000000000000000000001415564225700201545ustar00rootroot00000000000000lumino-2021.12.13/packages/virtualdom/src/index.ts000066400000000000000000001353051415564225700216420ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { ArrayExt } from '@lumino/algorithm'; /** * The names of the supported HTML5 DOM element attributes. * * This list is not all-encompassing, rather it attempts to define the * attribute names which are relevant for use in a virtual DOM context. * If a standardized or widely supported name is missing, please open * an issue to have it added. * * The attribute names were collected from the following sources: * - https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes * - https://www.w3.org/TR/html5/index.html#attributes-1 * - https://html.spec.whatwg.org/multipage/indices.html#attributes-3 */ export type ElementAttrNames = | 'abbr' | 'accept' | 'accept-charset' | 'accesskey' | 'action' | 'allowfullscreen' | 'alt' | 'autocomplete' | 'autofocus' | 'autoplay' | 'autosave' | 'checked' | 'cite' | 'cols' | 'colspan' | 'contenteditable' | 'controls' | 'coords' | 'crossorigin' | 'data' | 'datetime' | 'default' | 'dir' | 'dirname' | 'disabled' | 'download' | 'draggable' | 'dropzone' | 'enctype' | 'form' | 'formaction' | 'formenctype' | 'formmethod' | 'formnovalidate' | 'formtarget' | 'headers' | 'height' | 'hidden' | 'high' | 'href' | 'hreflang' | 'id' | 'inputmode' | 'integrity' | 'ismap' | 'kind' | 'label' | 'lang' | 'list' | 'loop' | 'low' | 'max' | 'maxlength' | 'media' | 'mediagroup' | 'method' | 'min' | 'minlength' | 'multiple' | 'muted' | 'name' | 'novalidate' | 'optimum' | 'pattern' | 'placeholder' | 'poster' | 'preload' | 'readonly' | 'rel' | 'required' | 'reversed' | 'rows' | 'rowspan' | 'sandbox' | 'scope' | 'selected' | 'shape' | 'size' | 'sizes' | 'span' | 'spellcheck' | 'src' | 'srcdoc' | 'srclang' | 'srcset' | 'start' | 'step' | 'tabindex' | 'target' | 'title' | 'type' | 'typemustmatch' | 'usemap' | 'value' | 'width' | 'wrap'; /** * The names of ARIA attributes for HTML elements. * * The attribute names are collected from * https://www.w3.org/TR/html5/infrastructure.html#element-attrdef-aria-role */ export type ARIAAttrNames = | 'aria-activedescendant' | 'aria-atomic' | 'aria-autocomplete' | 'aria-busy' | 'aria-checked' | 'aria-colcount' | 'aria-colindex' | 'aria-colspan' | 'aria-controls' | 'aria-current' | 'aria-describedby' | 'aria-details' | 'aria-dialog' | 'aria-disabled' | 'aria-dropeffect' | 'aria-errormessage' | 'aria-expanded' | 'aria-flowto' | 'aria-grabbed' | 'aria-haspopup' | 'aria-hidden' | 'aria-invalid' | 'aria-keyshortcuts' | 'aria-label' | 'aria-labelledby' | 'aria-level' | 'aria-live' | 'aria-multiline' | 'aria-multiselectable' | 'aria-orientation' | 'aria-owns' | 'aria-placeholder' | 'aria-posinset' | 'aria-pressed' | 'aria-readonly' | 'aria-relevant' | 'aria-required' | 'aria-roledescription' | 'aria-rowcount' | 'aria-rowindex' | 'aria-rowspan' | 'aria-selected' | 'aria-setsize' | 'aria-sort' | 'aria-valuemax' | 'aria-valuemin' | 'aria-valuenow' | 'aria-valuetext' | 'role'; /** * The names of the supported HTML5 CSS property names. * * If a standardized or widely supported name is missing, please open * an issue to have it added. * * The property names were collected from the following sources: * - TypeScript's `lib.dom.d.ts` file */ export type CSSPropertyNames = | 'alignContent' | 'alignItems' | 'alignSelf' | 'alignmentBaseline' | 'animation' | 'animationDelay' | 'animationDirection' | 'animationDuration' | 'animationFillMode' | 'animationIterationCount' | 'animationName' | 'animationPlayState' | 'animationTimingFunction' | 'backfaceVisibility' | 'background' | 'backgroundAttachment' | 'backgroundClip' | 'backgroundColor' | 'backgroundImage' | 'backgroundOrigin' | 'backgroundPosition' | 'backgroundPositionX' | 'backgroundPositionY' | 'backgroundRepeat' | 'backgroundSize' | 'baselineShift' | 'border' | 'borderBottom' | 'borderBottomColor' | 'borderBottomLeftRadius' | 'borderBottomRightRadius' | 'borderBottomStyle' | 'borderBottomWidth' | 'borderCollapse' | 'borderColor' | 'borderImage' | 'borderImageOutset' | 'borderImageRepeat' | 'borderImageSlice' | 'borderImageSource' | 'borderImageWidth' | 'borderLeft' | 'borderLeftColor' | 'borderLeftStyle' | 'borderLeftWidth' | 'borderRadius' | 'borderRight' | 'borderRightColor' | 'borderRightStyle' | 'borderRightWidth' | 'borderSpacing' | 'borderStyle' | 'borderTop' | 'borderTopColor' | 'borderTopLeftRadius' | 'borderTopRightRadius' | 'borderTopStyle' | 'borderTopWidth' | 'borderWidth' | 'bottom' | 'boxShadow' | 'boxSizing' | 'breakAfter' | 'breakBefore' | 'breakInside' | 'captionSide' | 'clear' | 'clip' | 'clipPath' | 'clipRule' | 'color' | 'colorInterpolationFilters' | 'columnCount' | 'columnFill' | 'columnGap' | 'columnRule' | 'columnRuleColor' | 'columnRuleStyle' | 'columnRuleWidth' | 'columnSpan' | 'columnWidth' | 'columns' | 'content' | 'counterIncrement' | 'counterReset' | 'cssFloat' | 'cssText' | 'cursor' | 'direction' | 'display' | 'dominantBaseline' | 'emptyCells' | 'enableBackground' | 'fill' | 'fillOpacity' | 'fillRule' | 'filter' | 'flex' | 'flexBasis' | 'flexDirection' | 'flexFlow' | 'flexGrow' | 'flexShrink' | 'flexWrap' | 'floodColor' | 'floodOpacity' | 'font' | 'fontFamily' | 'fontFeatureSettings' | 'fontSize' | 'fontSizeAdjust' | 'fontStretch' | 'fontStyle' | 'fontVariant' | 'fontWeight' | 'glyphOrientationHorizontal' | 'glyphOrientationVertical' | 'height' | 'imeMode' | 'justifyContent' | 'kerning' | 'left' | 'letterSpacing' | 'lightingColor' | 'lineHeight' | 'listStyle' | 'listStyleImage' | 'listStylePosition' | 'listStyleType' | 'margin' | 'marginBottom' | 'marginLeft' | 'marginRight' | 'marginTop' | 'marker' | 'markerEnd' | 'markerMid' | 'markerStart' | 'mask' | 'maxHeight' | 'maxWidth' | 'minHeight' | 'minWidth' | 'msContentZoomChaining' | 'msContentZoomLimit' | 'msContentZoomLimitMax' | 'msContentZoomLimitMin' | 'msContentZoomSnap' | 'msContentZoomSnapPoints' | 'msContentZoomSnapType' | 'msContentZooming' | 'msFlowFrom' | 'msFlowInto' | 'msFontFeatureSettings' | 'msGridColumn' | 'msGridColumnAlign' | 'msGridColumnSpan' | 'msGridColumns' | 'msGridRow' | 'msGridRowAlign' | 'msGridRowSpan' | 'msGridRows' | 'msHighContrastAdjust' | 'msHyphenateLimitChars' | 'msHyphenateLimitLines' | 'msHyphenateLimitZone' | 'msHyphens' | 'msImeAlign' | 'msOverflowStyle' | 'msScrollChaining' | 'msScrollLimit' | 'msScrollLimitXMax' | 'msScrollLimitXMin' | 'msScrollLimitYMax' | 'msScrollLimitYMin' | 'msScrollRails' | 'msScrollSnapPointsX' | 'msScrollSnapPointsY' | 'msScrollSnapType' | 'msScrollSnapX' | 'msScrollSnapY' | 'msScrollTranslation' | 'msTextCombineHorizontal' | 'msTextSizeAdjust' | 'msTouchAction' | 'msTouchSelect' | 'msUserSelect' | 'msWrapFlow' | 'msWrapMargin' | 'msWrapThrough' | 'opacity' | 'order' | 'orphans' | 'outline' | 'outlineColor' | 'outlineStyle' | 'outlineWidth' | 'overflow' | 'overflowX' | 'overflowY' | 'padding' | 'paddingBottom' | 'paddingLeft' | 'paddingRight' | 'paddingTop' | 'pageBreakAfter' | 'pageBreakBefore' | 'pageBreakInside' | 'perspective' | 'perspectiveOrigin' | 'pointerEvents' | 'position' | 'quotes' | 'resize' | 'right' | 'rubyAlign' | 'rubyOverhang' | 'rubyPosition' | 'stopColor' | 'stopOpacity' | 'stroke' | 'strokeDasharray' | 'strokeDashoffset' | 'strokeLinecap' | 'strokeLinejoin' | 'strokeMiterlimit' | 'strokeOpacity' | 'strokeWidth' | 'tableLayout' | 'textAlign' | 'textAlignLast' | 'textAnchor' | 'textDecoration' | 'textIndent' | 'textJustify' | 'textKashida' | 'textKashidaSpace' | 'textOverflow' | 'textShadow' | 'textTransform' | 'textUnderlinePosition' | 'top' | 'touchAction' | 'transform' | 'transformOrigin' | 'transformStyle' | 'transition' | 'transitionDelay' | 'transitionDuration' | 'transitionProperty' | 'transitionTimingFunction' | 'unicodeBidi' | 'verticalAlign' | 'visibility' | 'webkitAlignContent' | 'webkitAlignItems' | 'webkitAlignSelf' | 'webkitAnimation' | 'webkitAnimationDelay' | 'webkitAnimationDirection' | 'webkitAnimationDuration' | 'webkitAnimationFillMode' | 'webkitAnimationIterationCount' | 'webkitAnimationName' | 'webkitAnimationPlayState' | 'webkitAnimationTimingFunction' | 'webkitAppearance' | 'webkitBackfaceVisibility' | 'webkitBackgroundClip' | 'webkitBackgroundOrigin' | 'webkitBackgroundSize' | 'webkitBorderBottomLeftRadius' | 'webkitBorderBottomRightRadius' | 'webkitBorderImage' | 'webkitBorderRadius' | 'webkitBorderTopLeftRadius' | 'webkitBorderTopRightRadius' | 'webkitBoxAlign' | 'webkitBoxDirection' | 'webkitBoxFlex' | 'webkitBoxOrdinalGroup' | 'webkitBoxOrient' | 'webkitBoxPack' | 'webkitBoxSizing' | 'webkitColumnBreakAfter' | 'webkitColumnBreakBefore' | 'webkitColumnBreakInside' | 'webkitColumnCount' | 'webkitColumnGap' | 'webkitColumnRule' | 'webkitColumnRuleColor' | 'webkitColumnRuleStyle' | 'webkitColumnRuleWidth' | 'webkitColumnSpan' | 'webkitColumnWidth' | 'webkitColumns' | 'webkitFilter' | 'webkitFlex' | 'webkitFlexBasis' | 'webkitFlexDirection' | 'webkitFlexFlow' | 'webkitFlexGrow' | 'webkitFlexShrink' | 'webkitFlexWrap' | 'webkitJustifyContent' | 'webkitOrder' | 'webkitPerspective' | 'webkitPerspectiveOrigin' | 'webkitTapHighlightColor' | 'webkitTextFillColor' | 'webkitTextSizeAdjust' | 'webkitTransform' | 'webkitTransformOrigin' | 'webkitTransformStyle' | 'webkitTransition' | 'webkitTransitionDelay' | 'webkitTransitionDuration' | 'webkitTransitionProperty' | 'webkitTransitionTimingFunction' | 'webkitUserModify' | 'webkitUserSelect' | 'webkitWritingMode' | 'whiteSpace' | 'widows' | 'width' | 'wordBreak' | 'wordSpacing' | 'wordWrap' | 'writingMode' | 'zIndex' | 'zoom'; /** * A mapping of inline event name to event object type. * * This mapping is used to create the event listener properties for * the virtual DOM element attributes object. If a standardized or * widely supported name is missing, please open an issue to have it * added. * * The event names were collected from the following sources: * - TypeScript's `lib.dom.d.ts` file * - https://www.w3.org/TR/html5/index.html#attributes-1 * - https://html.spec.whatwg.org/multipage/webappapis.html#idl-definitions */ export type ElementEventMap = { onabort: UIEvent; onauxclick: MouseEvent; onblur: FocusEvent; oncanplay: Event; oncanplaythrough: Event; onchange: Event; onclick: MouseEvent; oncontextmenu: PointerEvent; oncopy: ClipboardEvent; oncuechange: Event; oncut: ClipboardEvent; ondblclick: MouseEvent; ondrag: DragEvent; ondragend: DragEvent; ondragenter: DragEvent; ondragexit: DragEvent; ondragleave: DragEvent; ondragover: DragEvent; ondragstart: DragEvent; ondrop: DragEvent; ondurationchange: Event; onemptied: Event; onended: ErrorEvent; onerror: ErrorEvent; onfocus: FocusEvent; oninput: Event; oninvalid: Event; onkeydown: KeyboardEvent; onkeypress: KeyboardEvent; onkeyup: KeyboardEvent; onload: Event; onloadeddata: Event; onloadedmetadata: Event; onloadend: Event; onloadstart: Event; onmousedown: MouseEvent; onmouseenter: MouseEvent; onmouseleave: MouseEvent; onmousemove: MouseEvent; onmouseout: MouseEvent; onmouseover: MouseEvent; onmouseup: MouseEvent; onmousewheel: WheelEvent; onpaste: ClipboardEvent; onpause: Event; onplay: Event; onplaying: Event; onpointercancel: PointerEvent; onpointerdown: PointerEvent; onpointerenter: PointerEvent; onpointerleave: PointerEvent; onpointermove: PointerEvent; onpointerout: PointerEvent; onpointerover: PointerEvent; onpointerup: PointerEvent; onprogress: ProgressEvent; onratechange: Event; onreset: Event; onscroll: UIEvent; onseeked: Event; onseeking: Event; onselect: UIEvent; onselectstart: Event; onstalled: Event; onsubmit: Event; onsuspend: Event; ontimeupdate: Event; onvolumechange: Event; onwaiting: Event; }; /** * An object which represents a dataset for a virtual DOM element. * * The names of the dataset properties will be automatically prefixed * with `data-` before being added to the node, e.g. `{ thing: '12' }` * will be rendered as `data-thing='12'` in the DOM element. * * Dataset property names should not contain spaces. */ export type ElementDataset = { readonly [name: string]: string; }; /** * The inline style for for a virtual DOM element. * * Style attributes use the JS camel-cased property names instead of * the CSS hyphenated names for performance and security. */ export type ElementInlineStyle = { readonly [T in CSSPropertyNames]?: string; }; /** * The ARIA attributes for a virtual element node. * * These are the attributes which are applied to a real DOM element via * `element.setAttribute()`. The supported attribute names are defined * by the `ARIAAttrNames` type. */ export type ElementARIAAttrs = { readonly [T in ARIAAttrNames]?: string; }; /** * The base attributes for a virtual element node. * * These are the attributes which are applied to a real DOM element via * `element.setAttribute()`. The supported attribute names are defined * by the `ElementAttrNames` type. * * Node attributes are specified using the lower-case HTML name instead * of the camel-case JS name due to browser inconsistencies in handling * the JS versions. */ export type ElementBaseAttrs = { readonly [T in ElementAttrNames]?: string; }; /** * The inline event listener attributes for a virtual element node. * * The supported listeners are defined by the `ElementEventMap` type. */ export type ElementEventAttrs = { readonly [T in keyof ElementEventMap]?: ( this: HTMLElement, event: ElementEventMap[T] ) => any; }; /** * The special-cased attributes for a virtual element node. */ export type ElementSpecialAttrs = { /** * The key id for the virtual element node. * * If a node is given a key id, the generated DOM node will not be * recreated during a rendering update if it only moves among its * siblings in the render tree. * * In general, reordering child nodes will cause the nodes to be * completely re-rendered. Keys allow this to be optimized away. * * If a key is provided, it must be unique among sibling nodes. */ readonly key?: string; /** * The JS-safe name for the HTML `class` attribute. */ readonly className?: string; /** * The JS-safe name for the HTML `for` attribute. */ readonly htmlFor?: string; /** * The dataset for the rendered DOM element. */ readonly dataset?: ElementDataset; /** * The inline style for the rendered DOM element. */ readonly style?: ElementInlineStyle; }; /** * The full set of attributes supported by a virtual element node. * * This is the combination of the base element attributes, the the ARIA attributes, * the inline element event listeners, and the special element attributes. */ export type ElementAttrs = ElementBaseAttrs & ElementARIAAttrs & ElementEventAttrs & ElementSpecialAttrs; /** * A virtual node which represents plain text content. * * #### Notes * User code will not typically create a `VirtualText` node directly. * Instead, the `h()` function will be used to create an element tree. */ export class VirtualText { /** * The text content for the node. */ readonly content: string; /** * The type of the node. * * This value can be used as a type guard for discriminating the * `VirtualNode` union type. */ readonly type: 'text' = 'text'; /** * Construct a new virtual text node. * * @param content - The text content for the node. */ constructor(content: string) { this.content = content; } } /** * A virtual node which represents an HTML element. * * #### Notes * User code will not typically create a `VirtualElement` node directly. * Instead, the `h()` function will be used to create an element tree. */ export class VirtualElement { /** * The tag name for the element. */ readonly tag: string; /** * The attributes for the element. */ readonly attrs: ElementAttrs; /** * The children for the element. */ readonly children: ReadonlyArray; /** * An optional custom renderer for the element's children. If set, on render * this element's DOM node and it's attrs will be created/updated as normal. * At that point the DOM node is handed off to the renderer. */ readonly renderer: VirtualElement.IRenderer | undefined; /** * The type of the node. * * This value can be used as a type guard for discriminating the * `VirtualNode` union type. */ readonly type: 'element' = 'element'; /** * Construct a new virtual element node. * * @param tag - The element tag name. * * @param attrs - The element attributes. * * @param children - The element children. * * @param renderer - An optional custom renderer for the element. */ constructor( tag: string, attrs: ElementAttrs, children: ReadonlyArray, renderer?: VirtualElement.IRenderer ) { this.tag = tag; this.attrs = attrs; this.children = children; this.renderer = renderer; } } export namespace VirtualElement { /** * A type describing a custom element renderer */ export type IRenderer = { /** * Customize how a DOM node is rendered. If .renderer is set on a given * instance of VirtualElement, this function will be called every time * that VirtualElement is rendered. * * @param host - The actual DOM node created for a VirtualElement during * rendering. * * On render, host is created and its attrs are set/updated via * the standard routines in updateContent. host is then handed off to this * function. * * The render function is free to modify host. The only restriction is * is that render should not modify any attributes set by external * routines (ie updateContent), as this may cause thrashing when the * virtual element is next rendered. * * @param options - Will be populated with the .attrs and .children fields * set on the VirtualElement being rendered. */ render: ( host: HTMLElement, options?: { attrs?: ElementAttrs; children?: ReadonlyArray } ) => void; /** * Optional cleanup function for custom renderers. If the .renderer field * of a VirtualELement is set, and if .renderer.unrender is defined, when * the element is changed or removed its corresponding DOM element will be * passed to this function immediately before it is removed from the DOM. * * unrender is not required for for simple renderers, such as those * implemented using `document.createElement()`. However, for certain * rendering techniques explicit cleanup is required in order to avoid * resource leaks. * * For example, if render calls `ReactDOM.render(..., host)`, then * there has to also be a corresponding implementation of unrender that * calls `ReactDOM.unmountComponentAtNode(host)` in order to prevent * a memory leak. * * @param host - the DOM element to be removed. * * @param options - Will be populated with the .attrs and .children fields * set on the VirtualElement being unrendered. */ unrender?: ( host: HTMLElement, options?: { attrs?: ElementAttrs; children?: ReadonlyArray } ) => void; }; } /** * DEPRECATED - use VirtualElement with a defined renderer param instead. * This class is provided as a backwards compatibility shim * * A "pass thru" virtual node whose children are managed by a render and an * unrender callback. The intent of this flavor of virtual node is to make * it easy to blend other kinds of virtualdom (eg React) into Phosphor's * virtualdom. * * #### Notes * User code will not typically create a `VirtualElementPass` node directly. * Instead, the `hpass()` function will be used to create an element tree. */ export class VirtualElementPass extends VirtualElement { /** * DEPRECATED - use VirtualElement with a defined renderer param instead * * Construct a new virtual element pass thru node. * * @param tag - the tag of the parent element of this node. Once the parent * element is rendered, it will be passed as an argument to * renderer.render * * @param attrs - attributes that will assigned to the * parent element * * @param renderer - an object with render and unrender * functions, each of which should take a single argument of type * HTMLElement and return nothing. If null, the parent element * will be rendered barren without any children. */ constructor( tag: string, attrs: ElementAttrs, renderer: VirtualElementPass.IRenderer | null ) { super(tag, attrs, [], renderer || undefined); } } export namespace VirtualElementPass { /** * DEPRECATED - use VirtualElement.IRenderer instead * * A type describing a custom element renderer */ export type IRenderer = VirtualElement.IRenderer; } /** * A type alias for a general virtual node. */ export type VirtualNode = VirtualElement | VirtualText; /** * Create a new virtual element node. * * @param tag - The tag name for the element. * * @param attrs - The attributes for the element, if any. * * @param renderer - An optional custom renderer for the element. * * @param children - The children for the element, if any. * * @returns A new virtual element node for the given parameters. * * #### Notes * The children may be string literals, other virtual nodes, `null`, or * an array of those things. Strings are converted into text nodes, and * arrays are inlined as if the array contents were given as positional * arguments. This makes it simple to build up an array of children by * any desired means. `null` child values are simply ignored. * * A bound function for each HTML tag name is available as a static * function attached to the `h()` function. E.g. `h('div', ...)` is * equivalent to `h.div(...)`. */ export function h(tag: string, ...children: h.Child[]): VirtualElement; export function h( tag: string, attrs: ElementAttrs, ...children: h.Child[] ): VirtualElement; export function h( tag: string, renderer: VirtualElement.IRenderer, ...children: h.Child[] ): VirtualElement; export function h( tag: string, attrs: ElementAttrs, renderer: VirtualElement.IRenderer, ...children: h.Child[] ): VirtualElement; export function h(tag: string): VirtualElement { let attrs: ElementAttrs = {}; let renderer: VirtualElement.IRenderer | undefined; let children: VirtualNode[] = []; for (let i = 1, n = arguments.length; i < n; ++i) { // eslint-disable-next-line prefer-rest-params let arg = arguments[i]; if (typeof arg === 'string') { children.push(new VirtualText(arg)); } else if (arg instanceof VirtualText) { children.push(arg); } else if (arg instanceof VirtualElement) { children.push(arg); } else if (arg instanceof Array) { extend(children, arg); } else if ((i === 1 || i === 2) && arg && typeof arg === 'object') { if ('render' in arg) { renderer = arg; } else { attrs = arg; } } } return new VirtualElement(tag, attrs, children, renderer); function extend(array: VirtualNode[], values: h.Child[]): void { for (let child of values) { if (typeof child === 'string') { array.push(new VirtualText(child)); } else if (child instanceof VirtualText) { array.push(child); } else if (child instanceof VirtualElement) { array.push(child); } } } } /** * The namespace for the `h` function statics. */ export namespace h { /** * A type alias for the supported child argument types. */ export type Child = | (string | VirtualNode | null) | Array; /** * A bound factory function for a specific `h()` tag. */ export interface IFactory { (...children: Child[]): VirtualElement; (attrs: ElementAttrs, ...children: Child[]): VirtualElement; ( renderer: VirtualElement.IRenderer, ...children: h.Child[] ): VirtualElement; ( attrs: ElementAttrs, renderer: VirtualElement.IRenderer, ...children: h.Child[] ): VirtualElement; } export const a: IFactory = h.bind(undefined, 'a'); export const abbr: IFactory = h.bind(undefined, 'abbr'); export const address: IFactory = h.bind(undefined, 'address'); export const area: IFactory = h.bind(undefined, 'area'); export const article: IFactory = h.bind(undefined, 'article'); export const aside: IFactory = h.bind(undefined, 'aside'); export const audio: IFactory = h.bind(undefined, 'audio'); export const b: IFactory = h.bind(undefined, 'b'); export const bdi: IFactory = h.bind(undefined, 'bdi'); export const bdo: IFactory = h.bind(undefined, 'bdo'); export const blockquote: IFactory = h.bind(undefined, 'blockquote'); export const br: IFactory = h.bind(undefined, 'br'); export const button: IFactory = h.bind(undefined, 'button'); export const canvas: IFactory = h.bind(undefined, 'canvas'); export const caption: IFactory = h.bind(undefined, 'caption'); export const cite: IFactory = h.bind(undefined, 'cite'); export const code: IFactory = h.bind(undefined, 'code'); export const col: IFactory = h.bind(undefined, 'col'); export const colgroup: IFactory = h.bind(undefined, 'colgroup'); export const data: IFactory = h.bind(undefined, 'data'); export const datalist: IFactory = h.bind(undefined, 'datalist'); export const dd: IFactory = h.bind(undefined, 'dd'); export const del: IFactory = h.bind(undefined, 'del'); export const dfn: IFactory = h.bind(undefined, 'dfn'); export const div: IFactory = h.bind(undefined, 'div'); export const dl: IFactory = h.bind(undefined, 'dl'); export const dt: IFactory = h.bind(undefined, 'dt'); export const em: IFactory = h.bind(undefined, 'em'); export const embed: IFactory = h.bind(undefined, 'embed'); export const fieldset: IFactory = h.bind(undefined, 'fieldset'); export const figcaption: IFactory = h.bind(undefined, 'figcaption'); export const figure: IFactory = h.bind(undefined, 'figure'); export const footer: IFactory = h.bind(undefined, 'footer'); export const form: IFactory = h.bind(undefined, 'form'); export const h1: IFactory = h.bind(undefined, 'h1'); export const h2: IFactory = h.bind(undefined, 'h2'); export const h3: IFactory = h.bind(undefined, 'h3'); export const h4: IFactory = h.bind(undefined, 'h4'); export const h5: IFactory = h.bind(undefined, 'h5'); export const h6: IFactory = h.bind(undefined, 'h6'); export const header: IFactory = h.bind(undefined, 'header'); export const hr: IFactory = h.bind(undefined, 'hr'); export const i: IFactory = h.bind(undefined, 'i'); export const iframe: IFactory = h.bind(undefined, 'iframe'); export const img: IFactory = h.bind(undefined, 'img'); export const input: IFactory = h.bind(undefined, 'input'); export const ins: IFactory = h.bind(undefined, 'ins'); export const kbd: IFactory = h.bind(undefined, 'kbd'); export const label: IFactory = h.bind(undefined, 'label'); export const legend: IFactory = h.bind(undefined, 'legend'); export const li: IFactory = h.bind(undefined, 'li'); export const main: IFactory = h.bind(undefined, 'main'); export const map: IFactory = h.bind(undefined, 'map'); export const mark: IFactory = h.bind(undefined, 'mark'); export const meter: IFactory = h.bind(undefined, 'meter'); export const nav: IFactory = h.bind(undefined, 'nav'); export const noscript: IFactory = h.bind(undefined, 'noscript'); export const object: IFactory = h.bind(undefined, 'object'); export const ol: IFactory = h.bind(undefined, 'ol'); export const optgroup: IFactory = h.bind(undefined, 'optgroup'); export const option: IFactory = h.bind(undefined, 'option'); export const output: IFactory = h.bind(undefined, 'output'); export const p: IFactory = h.bind(undefined, 'p'); export const param: IFactory = h.bind(undefined, 'param'); export const pre: IFactory = h.bind(undefined, 'pre'); export const progress: IFactory = h.bind(undefined, 'progress'); export const q: IFactory = h.bind(undefined, 'q'); export const rp: IFactory = h.bind(undefined, 'rp'); export const rt: IFactory = h.bind(undefined, 'rt'); export const ruby: IFactory = h.bind(undefined, 'ruby'); export const s: IFactory = h.bind(undefined, 's'); export const samp: IFactory = h.bind(undefined, 'samp'); export const section: IFactory = h.bind(undefined, 'section'); export const select: IFactory = h.bind(undefined, 'select'); export const small: IFactory = h.bind(undefined, 'small'); export const source: IFactory = h.bind(undefined, 'source'); export const span: IFactory = h.bind(undefined, 'span'); export const strong: IFactory = h.bind(undefined, 'strong'); export const sub: IFactory = h.bind(undefined, 'sub'); export const summary: IFactory = h.bind(undefined, 'summary'); export const sup: IFactory = h.bind(undefined, 'sup'); export const table: IFactory = h.bind(undefined, 'table'); export const tbody: IFactory = h.bind(undefined, 'tbody'); export const td: IFactory = h.bind(undefined, 'td'); export const textarea: IFactory = h.bind(undefined, 'textarea'); export const tfoot: IFactory = h.bind(undefined, 'tfoot'); export const th: IFactory = h.bind(undefined, 'th'); export const thead: IFactory = h.bind(undefined, 'thead'); export const time: IFactory = h.bind(undefined, 'time'); export const title: IFactory = h.bind(undefined, 'title'); export const tr: IFactory = h.bind(undefined, 'tr'); export const track: IFactory = h.bind(undefined, 'track'); export const u: IFactory = h.bind(undefined, 'u'); export const ul: IFactory = h.bind(undefined, 'ul'); export const var_: IFactory = h.bind(undefined, 'var'); export const video: IFactory = h.bind(undefined, 'video'); export const wbr: IFactory = h.bind(undefined, 'wbr'); } /** * DEPRECATED - pass the renderer arg to the h function instead * * Create a new "pass thru" virtual element node. * * @param tag - The tag name for the parent element. * * @param attrs - The attributes for the parent element, if any. * * @param renderer - an object with render and unrender functions, if any. * * @returns A new "pass thru" virtual element node for the given parameters. * */ export function hpass( tag: string, renderer?: VirtualElementPass.IRenderer ): VirtualElementPass; export function hpass( tag: string, attrs: ElementAttrs, renderer?: VirtualElementPass.IRenderer ): VirtualElementPass; export function hpass(tag: string): VirtualElementPass { let attrs: ElementAttrs = {}; let renderer: VirtualElementPass.IRenderer | null = null; if (arguments.length === 2) { // eslint-disable-next-line prefer-rest-params const arg = arguments[1]; if ('render' in arg) { renderer = arg; } else { attrs = arg; } } else if (arguments.length === 3) { // eslint-disable-next-line prefer-rest-params attrs = arguments[1]; // eslint-disable-next-line prefer-rest-params renderer = arguments[2]; } else if (arguments.length > 3) { throw new Error('hpass() should be called with 1, 2, or 3 arguments'); } return new VirtualElementPass(tag, attrs, renderer); } /** * The namespace for the virtual DOM rendering functions. */ export namespace VirtualDOM { /** * Create a real DOM element from a virtual element node. * * @param node - The virtual element node to realize. * * @returns A new DOM element for the given virtual element node. * * #### Notes * This creates a brand new *real* DOM element with a structure which * matches the given virtual DOM node. * * If virtual diffing is desired, use the `render` function instead. */ export function realize(node: VirtualText): Text; export function realize(node: VirtualElement): HTMLElement; export function realize(node: VirtualNode): HTMLElement | Text { return Private.createDOMNode(node); } /** * Render virtual DOM content into a host element. * * @param content - The virtual DOM content to render. * * @param host - The host element for the rendered content. * * #### Notes * This renders the delta from the previous rendering. It assumes that * the content of the host element is not manipulated by external code. * * Providing `null` content will clear the rendering. * * Externally modifying the provided content or the host element will * result in undefined rendering behavior. */ export function render( content: VirtualNode | ReadonlyArray | null, host: HTMLElement ): void { let oldContent = Private.hostMap.get(host) || []; let newContent = Private.asContentArray(content); Private.hostMap.set(host, newContent); Private.updateContent(host, oldContent, newContent); } } /** * The namespace for the module implementation details. */ namespace Private { /** * A weak mapping of host element to virtual DOM content. */ export const hostMap = new WeakMap>(); /** * Cast a content value to a content array. */ export function asContentArray( value: VirtualNode | ReadonlyArray | null ): ReadonlyArray { if (!value) { return []; } if (value instanceof Array) { return value as ReadonlyArray; } return [value as VirtualNode]; } /** * Create a new DOM element for a virtual node. */ export function createDOMNode(node: VirtualText): Text; export function createDOMNode(node: VirtualElement): HTMLElement; export function createDOMNode(node: VirtualNode): HTMLElement | Text; export function createDOMNode( node: VirtualNode, host: HTMLElement | null ): HTMLElement | Text; export function createDOMNode( node: VirtualNode, host: HTMLElement | null, before: Node | null ): HTMLElement | Text; export function createDOMNode(node: VirtualNode): HTMLElement | Text { // eslint-disable-next-line prefer-rest-params let host = arguments[1] || null; // eslint-disable-next-line prefer-rest-params const before = arguments[2] || null; if (host) { host.insertBefore(createDOMNode(node), before); } else { // Create a text node for a virtual text node. if (node.type === 'text') { return document.createTextNode(node.content); } // Create the HTML element with the specified tag. host = document.createElement(node.tag); // Add the attributes for the new element. addAttrs(host, node.attrs); if (node.renderer) { node.renderer.render(host, { attrs: node.attrs, children: node.children }); return host; } // Recursively populate the element with child content. for (let i = 0, n = node.children.length; i < n; ++i) { createDOMNode(node.children[i], host); } } return host; } /** * Update a host element with the delta of the virtual content. * * This is the core "diff" algorithm. There is no explicit "patch" * phase. The host is patched at each step as the diff progresses. */ export function updateContent( host: HTMLElement, oldContent: ReadonlyArray, newContent: ReadonlyArray ): void { // Bail early if the content is identical. if (oldContent === newContent) { return; } // Collect the old keyed elems into a mapping. let oldKeyed = collectKeys(host, oldContent); // Create a copy of the old content which can be modified in-place. let oldCopy = oldContent.slice(); // Update the host with the new content. The diff always proceeds // forward and never modifies a previously visited index. The old // copy array is modified in-place to reflect the changes made to // the host children. This causes the stale nodes to be pushed to // the end of the host node and removed at the end of the loop. let currElem = host.firstChild; let newCount = newContent.length; for (let i = 0; i < newCount; ++i) { // If the old content is exhausted, create a new node. if (i >= oldCopy.length) { createDOMNode(newContent[i], host); continue; } // Lookup the old and new virtual nodes. let oldVNode = oldCopy[i]; let newVNode = newContent[i]; // If both elements are identical, there is nothing to do. if (oldVNode === newVNode) { currElem = currElem!.nextSibling; continue; } // Handle the simplest case of in-place text update first. if (oldVNode.type === 'text' && newVNode.type === 'text') { currElem!.textContent = newVNode.content; currElem = currElem!.nextSibling; continue; } // If the old or new node is a text node, the other node is now // known to be an element node, so create and insert a new node. if (oldVNode.type === 'text' || newVNode.type === 'text') { ArrayExt.insert(oldCopy, i, newVNode); createDOMNode(newVNode, host, currElem); continue; } // If the old XOR new node has a custom renderer, // create and insert a new node. if (!oldVNode.renderer != !newVNode.renderer) { ArrayExt.insert(oldCopy, i, newVNode); createDOMNode(newVNode, host, currElem); continue; } // At this point, both nodes are known to be element nodes. // If the new elem is keyed, move an old keyed elem to the proper // location before proceeding with the diff. The search can start // at the current index, since the unmatched old keyed elems are // pushed forward in the old copy array. let newKey = newVNode.attrs.key; if (newKey && newKey in oldKeyed) { let pair = oldKeyed[newKey]; if (pair.vNode !== oldVNode) { ArrayExt.move(oldCopy, oldCopy.indexOf(pair.vNode, i + 1), i); host.insertBefore(pair.element, currElem); oldVNode = pair.vNode; currElem = pair.element; } } // If both elements are identical, there is nothing to do. if (oldVNode === newVNode) { currElem = currElem!.nextSibling; continue; } // If the old elem is keyed and does not match the new elem key, // create a new node. This is necessary since the old keyed elem // may be matched at a later point in the diff. let oldKey = oldVNode.attrs.key; if (oldKey && oldKey !== newKey) { ArrayExt.insert(oldCopy, i, newVNode); createDOMNode(newVNode, host, currElem); continue; } // If the tags are different, create a new node. if (oldVNode.tag !== newVNode.tag) { ArrayExt.insert(oldCopy, i, newVNode); createDOMNode(newVNode, host, currElem); continue; } // At this point, the element can be updated in-place. // Update the element attributes. updateAttrs(currElem as HTMLElement, oldVNode.attrs, newVNode.attrs); // Update the element content. if (newVNode.renderer) { newVNode.renderer.render(currElem as HTMLElement, { attrs: newVNode.attrs, children: newVNode.children }); } else { updateContent( currElem as HTMLElement, oldVNode.children, newVNode.children ); } // Step to the next sibling element. currElem = currElem!.nextSibling; } // Cleanup stale DOM removeContent(host, oldCopy, newCount, true); } /** * Handle cleanup of stale vdom and its associated DOM. The host node is * traversed recursively (in depth-first order), and any explicit cleanup * required by a child node is carried out when it is visited (eg if a node * has a custom renderer, the renderer.unrender function will be called). * Once the subtree beneath each child of host has been completely visited, * that child will be removed via a call to host.removeChild. */ function removeContent( host: HTMLElement, oldContent: ReadonlyArray, newCount: number, _sentinel: boolean ) { // Dispose of the old nodes pushed to the end of the host. for (let i = oldContent.length - 1; i >= newCount; --i) { const oldNode = oldContent[i]; const child = (_sentinel ? host.lastChild : host.childNodes[i]) as HTMLElement; // recursively clean up host children if (oldNode.type === 'text') { // pass } else if (oldNode.renderer && oldNode.renderer.unrender) { oldNode.renderer.unrender(child!, { attrs: oldNode.attrs, children: oldNode.children }); } else { removeContent(child!, oldNode.children, 0, false); } if (_sentinel) { host.removeChild(child!); } } } /** * A set of special-cased attribute names. */ const specialAttrs = { key: true, className: true, htmlFor: true, dataset: true, style: true }; /** * Add element attributes to a newly created HTML element. */ function addAttrs(element: HTMLElement, attrs: ElementAttrs): void { // Add the inline event listeners and node attributes. for (let name in attrs) { if (name in specialAttrs) { continue; } if (name.substr(0, 2) === 'on') { (element as any)[name] = (attrs as any)[name]; } else { element.setAttribute(name, (attrs as any)[name]); } } // Add the element `class` attribute. if (attrs.className !== undefined) { element.setAttribute('class', attrs.className); } // Add the element `for` attribute. if (attrs.htmlFor !== undefined) { element.setAttribute('for', attrs.htmlFor); } // Add the dataset values. if (attrs.dataset) { addDataset(element, attrs.dataset); } // Add the inline styles. if (attrs.style) { addStyle(element, attrs.style); } } /** * Update the element attributes of an HTML element. */ function updateAttrs( element: HTMLElement, oldAttrs: ElementAttrs, newAttrs: ElementAttrs ): void { // Do nothing if the attrs are the same object. if (oldAttrs === newAttrs) { return; } // Setup the strongly typed loop variable. let name: keyof ElementAttrs; // Remove attributes and listeners which no longer exist. for (name in oldAttrs) { if (name in specialAttrs || name in newAttrs) { continue; } if (name.substr(0, 2) === 'on') { (element as any)[name] = null; } else { element.removeAttribute(name); } } // Add and update new and existing attributes and listeners. for (name in newAttrs) { if (name in specialAttrs || oldAttrs[name] === newAttrs[name]) { continue; } if (name.substr(0, 2) === 'on') { (element as any)[name] = (newAttrs as any)[name]; } else { element.setAttribute(name, (newAttrs as any)[name]); } } // Update the element `class` attribute. if (oldAttrs.className !== newAttrs.className) { if (newAttrs.className !== undefined) { element.setAttribute('class', newAttrs.className); } else { element.removeAttribute('class'); } } // Add the element `for` attribute. if (oldAttrs.htmlFor !== newAttrs.htmlFor) { if (newAttrs.htmlFor !== undefined) { element.setAttribute('for', newAttrs.htmlFor); } else { element.removeAttribute('for'); } } // Update the dataset values. if (oldAttrs.dataset !== newAttrs.dataset) { updateDataset(element, oldAttrs.dataset || {}, newAttrs.dataset || {}); } // Update the inline styles. if (oldAttrs.style !== newAttrs.style) { updateStyle(element, oldAttrs.style || {}, newAttrs.style || {}); } } /** * Add dataset values to a newly created HTML element. */ function addDataset(element: HTMLElement, dataset: ElementDataset): void { for (let name in dataset) { element.setAttribute(`data-${name}`, dataset[name]); } } /** * Update the dataset values of an HTML element. */ function updateDataset( element: HTMLElement, oldDataset: ElementDataset, newDataset: ElementDataset ): void { for (let name in oldDataset) { if (!(name in newDataset)) { element.removeAttribute(`data-${name}`); } } for (let name in newDataset) { if (oldDataset[name] !== newDataset[name]) { element.setAttribute(`data-${name}`, newDataset[name]); } } } /** * Add inline style values to a newly created HTML element. */ function addStyle(element: HTMLElement, style: ElementInlineStyle): void { let elemStyle = element.style; let name: keyof ElementInlineStyle; for (name in style) { (elemStyle as any)[name] = style[name]; } } /** * Update the inline style values of an HTML element. */ function updateStyle( element: HTMLElement, oldStyle: ElementInlineStyle, newStyle: ElementInlineStyle ): void { let elemStyle = element.style; let name: keyof ElementInlineStyle; for (name in oldStyle) { if (!(name in newStyle)) { (elemStyle as any)[name] = ''; } } for (name in newStyle) { if (oldStyle[name] !== newStyle[name]) { (elemStyle as any)[name] = newStyle[name]; } } } /** * A mapping of string key to pair of element and rendered node. */ type KeyMap = { [key: string]: { vNode: VirtualElement; element: HTMLElement }; }; /** * Collect a mapping of keyed elements for the host content. */ function collectKeys( host: HTMLElement, content: ReadonlyArray ): KeyMap { let node = host.firstChild; let keyMap: KeyMap = Object.create(null); for (let vNode of content) { if (vNode.type === 'element' && vNode.attrs.key) { keyMap[vNode.attrs.key] = { vNode, element: node as HTMLElement }; } node = node!.nextSibling; } return keyMap; } } lumino-2021.12.13/packages/virtualdom/tdoptions.json000066400000000000000000000005211415564225700223010ustar00rootroot00000000000000{ "excludeNotExported": true, "mode": "file", "target": "es5", "module": "es5", "lib": [ "lib.es2015.d.ts", "lib.es2015.collection.d.ts", "lib.es2015.promise.d.ts", "lib.dom.d.ts" ], "out": "../../docs/source/api/virtualdom", "baseUrl": ".", "paths": { "@lumino/*": ["node_modules/@lumino/*"] } } lumino-2021.12.13/packages/virtualdom/tests/000077500000000000000000000000001415564225700205275ustar00rootroot00000000000000lumino-2021.12.13/packages/virtualdom/tests/karma.conf.js000066400000000000000000000005051415564225700231040ustar00rootroot00000000000000module.exports = function (config) { config.set({ basePath: '.', frameworks: ['mocha'], reporters: ['mocha'], files: ['build/bundle.test.js'], port: 9876, colors: true, singleRun: true, browserNoActivityTimeout: 30000, failOnEmptyTestSuite: false, logLevel: config.LOG_INFO }); }; lumino-2021.12.13/packages/virtualdom/tests/src/000077500000000000000000000000001415564225700213165ustar00rootroot00000000000000lumino-2021.12.13/packages/virtualdom/tests/src/index.spec.ts000066400000000000000000000513161415564225700237340ustar00rootroot00000000000000/* eslint-disable @typescript-eslint/no-empty-function */ // Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { expect } from 'chai'; import { h, VirtualDOM, VirtualElement, VirtualText } from '@lumino/virtualdom'; describe('@lumino/virtualdom', () => { describe('VirtualText', () => { describe('#constructor()', () => { it('should create a virtual text node', () => { let vnode = new VirtualText('foo'); expect(vnode).to.be.an.instanceof(VirtualText); }); }); describe('#type', () => { it('should be `text`', () => { let vnode = new VirtualText('foo'); expect(vnode.type).to.equal('text'); }); }); describe('#content', () => { it('should be the text content', () => { let vnode = new VirtualText('foo'); expect(vnode.content).to.equal('foo'); }); }); }); describe('VirtualElement', () => { describe('#constructor()', () => { it('should create a virtual element node', () => { let vnode = new VirtualElement('img', {}, []); expect(vnode).to.be.an.instanceof(VirtualElement); }); }); describe('#type', () => { it('should be `element`', () => { let vnode = new VirtualElement('img', {}, []); expect(vnode.type).to.equal('element'); }); }); describe('#tag', () => { it('should be the element tag name', () => { let vnode = new VirtualElement('img', {}, []); expect(vnode.tag).to.equal('img'); }); }); describe('#attrs', () => { it('should be the element attrs', () => { let attrs = { className: 'bar' }; let vnode = new VirtualElement('img', attrs, []); expect(vnode.attrs).to.deep.equal(attrs); }); }); describe('#children', () => { it('should be the element children', () => { let children = [h.a(), h.img()]; let vnode = new VirtualElement('div', {}, children); expect(vnode.children).to.equal(children); }); }); }); describe('VirtualElement with custom .renderer', () => { let mockRenderer = { render: (host: HTMLElement) => {}, unrender: (host: HTMLElement) => {} }; describe('#constructor()', () => { it('should create a virtual element node', () => { let vnode = new VirtualElement('div', {}, [], mockRenderer); expect(vnode).to.be.an.instanceof(VirtualElement); }); }); describe('#type', () => { it('should be `element`', () => { let vnode = new VirtualElement('div', {}, [], mockRenderer); expect(vnode.type).to.equal('element'); }); }); describe('#tag', () => { it('should be the element tag name', () => { let vnode = new VirtualElement('img', {}, [], mockRenderer); expect(vnode.tag).to.equal('img'); }); }); describe('#attrs', () => { it('should be the element attrs', () => { let attrs = { className: 'baz' }; let vnode = new VirtualElement('img', attrs, [], mockRenderer); expect(vnode.attrs).to.deep.equal(attrs); }); }); describe('#renderer', () => { it('should be the element children renderer', () => { let vnode = new VirtualElement('div', {}, [], mockRenderer); expect(vnode.renderer!.render).to.equal(mockRenderer.render); expect(vnode.renderer!.unrender).to.equal(mockRenderer.unrender); }); }); }); describe('h()', () => { it('should create a new virtual element node', () => { let vnode = h('a'); expect(vnode).to.be.an.instanceof(VirtualElement); }); it('should accept string literals for children and convert them to text nodes', () => { let vnode = h('div', {}, ['foo', 'bar']); expect(vnode.children[0]).to.be.an.instanceof(VirtualText); expect(vnode.children[1]).to.be.an.instanceof(VirtualText); expect(vnode.children[0].type).to.equal('text'); expect(vnode.children[1].type).to.equal('text'); expect((vnode.children[0] as VirtualText).content).to.equal('foo'); expect((vnode.children[1] as VirtualText).content).to.equal('bar'); }); it('should accept other virtual DOM nodes for children', () => { let children = [h('a'), h('img')]; let vnode = h('div', {}, children); expect(vnode.children[0]).to.equal(children[0]); expect(vnode.children[1]).to.equal(children[1]); expect(vnode.children[0].type).to.equal('element'); expect(vnode.children[1].type).to.equal('element'); expect((vnode.children[0] as VirtualElement).tag).to.equal('a'); expect((vnode.children[1] as VirtualElement).tag).to.equal('img'); }); it('should accept a mix of string literals and virtual DOM nodes', () => { let children = ['foo', h('img')]; let vnode = h('div', {}, children); expect(vnode.children[1]).to.equal(children[1]); expect(vnode.children[0].type).to.equal('text'); expect((vnode.children[0] as VirtualText).content).to.equal('foo'); expect(vnode.children[1].type).to.equal('element'); expect((vnode.children[1] as VirtualElement).tag).to.equal('img'); }); it('should ignore `null` child values', () => { let children = ['foo', null, h('img')]; let vnode = h('div', {}, children); expect(vnode.children[1]).to.equal(children[2]); expect(vnode.children[0].type).to.equal('text'); expect((vnode.children[0] as VirtualText).content).to.equal('foo'); expect(vnode.children[1].type).to.equal('element'); expect((vnode.children[1] as VirtualElement).tag).to.equal('img'); }); it('should accept a string as the second argument', () => { let vnode = h('div', 'foo'); expect(vnode.children[0].type).to.equal('text'); expect((vnode.children[0] as VirtualText).content).to.equal('foo'); }); it('should accept a virtual node as the second argument', () => { let vnode = h('div', h('a')); expect(vnode.children[0].type).to.equal('element'); expect((vnode.children[0] as VirtualElement).tag).to.equal('a'); }); it('should accept an array as the second argument', () => { let children = [h('a'), h('img')]; let vnode = h('div', children); expect(vnode.children[0]).to.equal(children[0]); expect(vnode.children[0].type).to.equal('element'); expect((vnode.children[0] as VirtualElement).tag).to.equal('a'); expect(vnode.children[1].type).to.equal('element'); expect((vnode.children[1] as VirtualElement).tag).to.equal('img'); }); it('should accept other nodes as variadic args', () => { let vnode = h('div', h('a'), h('img')); expect(vnode.children[0].type).to.equal('element'); expect((vnode.children[0] as VirtualElement).tag).to.equal('a'); expect(vnode.children[1].type).to.equal('element'); expect((vnode.children[1] as VirtualElement).tag).to.equal('img'); }); it('should set the attrs directly', () => { let attrs = { style: { color: 'red' }, dataset: { a: '1' } }; let vnode = h('img', attrs); expect(vnode.attrs).to.deep.equal(attrs); }); }); describe('h', () => { it('should create the appropriate element tag', () => { expect(h.a().tag).to.equal('a'); expect(h.abbr().tag).to.equal('abbr'); expect(h.address().tag).to.equal('address'); expect(h.area().tag).to.equal('area'); expect(h.article().tag).to.equal('article'); expect(h.aside().tag).to.equal('aside'); expect(h.audio().tag).to.equal('audio'); expect(h.b().tag).to.equal('b'); expect(h.bdi().tag).to.equal('bdi'); expect(h.bdo().tag).to.equal('bdo'); expect(h.blockquote().tag).to.equal('blockquote'); expect(h.br().tag).to.equal('br'); expect(h.button().tag).to.equal('button'); expect(h.canvas().tag).to.equal('canvas'); expect(h.caption().tag).to.equal('caption'); expect(h.cite().tag).to.equal('cite'); expect(h.code().tag).to.equal('code'); expect(h.col().tag).to.equal('col'); expect(h.colgroup().tag).to.equal('colgroup'); expect(h.data().tag).to.equal('data'); expect(h.datalist().tag).to.equal('datalist'); expect(h.dd().tag).to.equal('dd'); expect(h.del().tag).to.equal('del'); expect(h.dfn().tag).to.equal('dfn'); expect(h.div().tag).to.equal('div'); expect(h.dl().tag).to.equal('dl'); expect(h.dt().tag).to.equal('dt'); expect(h.em().tag).to.equal('em'); expect(h.embed().tag).to.equal('embed'); expect(h.fieldset().tag).to.equal('fieldset'); expect(h.figcaption().tag).to.equal('figcaption'); expect(h.figure().tag).to.equal('figure'); expect(h.footer().tag).to.equal('footer'); expect(h.form().tag).to.equal('form'); expect(h.h1().tag).to.equal('h1'); expect(h.h2().tag).to.equal('h2'); expect(h.h3().tag).to.equal('h3'); expect(h.h4().tag).to.equal('h4'); expect(h.h5().tag).to.equal('h5'); expect(h.h6().tag).to.equal('h6'); expect(h.header().tag).to.equal('header'); expect(h.hr().tag).to.equal('hr'); expect(h.i().tag).to.equal('i'); expect(h.iframe().tag).to.equal('iframe'); expect(h.img().tag).to.equal('img'); expect(h.input().tag).to.equal('input'); expect(h.ins().tag).to.equal('ins'); expect(h.kbd().tag).to.equal('kbd'); expect(h.label().tag).to.equal('label'); expect(h.legend().tag).to.equal('legend'); expect(h.li().tag).to.equal('li'); expect(h.main().tag).to.equal('main'); expect(h.map().tag).to.equal('map'); expect(h.mark().tag).to.equal('mark'); expect(h.meter().tag).to.equal('meter'); expect(h.nav().tag).to.equal('nav'); expect(h.noscript().tag).to.equal('noscript'); expect(h.object().tag).to.equal('object'); expect(h.ol().tag).to.equal('ol'); expect(h.optgroup().tag).to.equal('optgroup'); expect(h.option().tag).to.equal('option'); expect(h.output().tag).to.equal('output'); expect(h.p().tag).to.equal('p'); expect(h.param().tag).to.equal('param'); expect(h.pre().tag).to.equal('pre'); expect(h.progress().tag).to.equal('progress'); expect(h.q().tag).to.equal('q'); expect(h.rp().tag).to.equal('rp'); expect(h.rt().tag).to.equal('rt'); expect(h.ruby().tag).to.equal('ruby'); expect(h.s().tag).to.equal('s'); expect(h.samp().tag).to.equal('samp'); expect(h.section().tag).to.equal('section'); expect(h.select().tag).to.equal('select'); expect(h.small().tag).to.equal('small'); expect(h.source().tag).to.equal('source'); expect(h.span().tag).to.equal('span'); expect(h.strong().tag).to.equal('strong'); expect(h.sub().tag).to.equal('sub'); expect(h.summary().tag).to.equal('summary'); expect(h.sup().tag).to.equal('sup'); expect(h.table().tag).to.equal('table'); expect(h.tbody().tag).to.equal('tbody'); expect(h.td().tag).to.equal('td'); expect(h.textarea().tag).to.equal('textarea'); expect(h.tfoot().tag).to.equal('tfoot'); expect(h.th().tag).to.equal('th'); expect(h.thead().tag).to.equal('thead'); expect(h.time().tag).to.equal('time'); expect(h.title().tag).to.equal('title'); expect(h.tr().tag).to.equal('tr'); expect(h.track().tag).to.equal('track'); expect(h.u().tag).to.equal('u'); expect(h.ul().tag).to.equal('ul'); expect(h.var_().tag).to.equal('var'); expect(h.video().tag).to.equal('video'); expect(h.wbr().tag).to.equal('wbr'); }); }); describe('h() with IRenderer param', () => { let tag = 'div'; let attrs = { className: 'baz' }; let mockRenderer = { render: (host: HTMLElement) => {}, unrender: (host: HTMLElement) => {} }; it('should create a new virtual element with custom renderer', () => { let vnode = h(tag, attrs, mockRenderer); expect(vnode).to.be.an.instanceof(VirtualElement); expect(vnode.tag).to.equal(tag); expect(vnode.attrs).to.deep.equal(attrs); expect(vnode.renderer!.render).to.equal(mockRenderer.render); expect(vnode.renderer!.unrender).to.equal(mockRenderer.unrender); }); it('should create a virtual element with custom renderer and without attrs', () => { let vnode = h('div', mockRenderer); expect(vnode).to.be.an.instanceof(VirtualElement); expect(vnode.tag).to.equal('div'); expect(vnode.attrs).to.deep.equal({}); expect(vnode.renderer!.render).to.equal(mockRenderer.render); expect(vnode.renderer!.unrender).to.equal(mockRenderer.unrender); }); it('should create a virtual element without custom renderer and with attrs', () => { let vnode = h('div', attrs); expect(vnode).to.be.an.instanceof(VirtualElement); expect(vnode.tag).to.equal(tag); expect(vnode.attrs).to.deep.equal(attrs); expect(vnode.renderer).to.equal(undefined); }); it('should create a virtual element without custom renderer or attrs', () => { let vnode = h('div'); expect(vnode).to.be.an.instanceof(VirtualElement); expect(vnode.tag).to.equal('div'); expect(vnode.attrs).to.deep.equal({}); expect(vnode.renderer).to.equal(undefined); }); }); describe('VirtualDOM', () => { describe('realize()', () => { it('should create a real DOM node from a virtual DOM node', () => { let node = VirtualDOM.realize(h.div([h.a(), h.img()])); expect(node.nodeName.toLowerCase()).to.equal('div'); expect(node.children[0].nodeName.toLowerCase()).to.equal('a'); expect(node.children[1].nodeName.toLowerCase()).to.equal('img'); }); }); describe('render()', () => { it('should render virtual DOM content into a host elememnt', () => { let host = document.createElement('div'); VirtualDOM.render(h.img(), host); expect(host.children[0].nodeName.toLowerCase()).to.equal('img'); }); it('should render the delta from the previous rendering', () => { let host = document.createElement('div'); let children = [h.a(), h.span(), h.img()]; VirtualDOM.render(children, host); let first = host.children[0]; let last = host.children[2]; expect(first.nodeName.toLowerCase()).to.equal('a'); expect(last.nodeName.toLowerCase()).to.equal('img'); children = [children[0], h.div(), children[1]]; VirtualDOM.render(children, host); expect(host.children[0]).to.equal(first); expect(host.children[2]).to.not.equal(last); expect(host.children[2].nodeName.toLowerCase()).to.equal('span'); }); it('should clear the rendering if `null` content is provided', () => { let host = document.createElement('div'); VirtualDOM.render(h('div', ['bar', 'foo']), host); expect(host.children[0].childNodes.length).to.equal(2); VirtualDOM.render(null, host); expect(host.children.length).to.equal(0); }); it('should update attributes', () => { let host = document.createElement('div'); let attrs1 = { alt: 'foo', height: '100', style: { color: 'white' }, dataset: { foo: '2', bar: '2' }, onload: () => {}, srcset: 'foo' }; let attrs2 = { alt: 'bar', width: '100', style: { border: '1px' }, dataset: { bar: '1', baz: '3' }, sizes: 'baz' }; VirtualDOM.render([h.a(), h.img(attrs1)], host); VirtualDOM.render([h.a(), h.img(attrs2)], host); expect((host.children[1] as HTMLImageElement).alt).to.equal('bar'); }); it('should not recreate a DOM node that moves if it has a key id', () => { let host = document.createElement('div'); let children1 = [ h.span({ key: '1' }), h.span({ key: '2' }), h.span({ key: '3' }), h.span({ key: '4' }) ]; let children2 = [ h.span({ key: '1' }), h.span({ key: '3' }), h.span({ key: '2' }), h.span({ key: '4' }) ]; VirtualDOM.render(children1, host); let child1 = host.children[1]; let child2 = host.children[2]; VirtualDOM.render(children2, host); expect(host.children[1]).to.equal(child2); expect(host.children[2]).to.equal(child1); }); it('should still recreate the DOM node if the node type changes', () => { let host = document.createElement('div'); let children1 = [ h.span({ key: '1' }), h.span({ key: '2' }), h.span({ key: '3' }), h.span({ key: '4' }) ]; let children2 = [ h.span({ key: '1' }), h.div({ key: '3' }), h.span({ key: '2' }), h.span({ key: '4' }) ]; VirtualDOM.render(children1, host); VirtualDOM.render(children2, host); expect(host.children[1].nodeName.toLowerCase()).to.equal('div'); }); it('should handle a new keyed item', () => { let host = document.createElement('div'); let children1 = [ h.span({ key: '1' }), h.span({ key: '2' }), h.span({ key: '3' }), h.span({ key: '4' }) ]; let children2 = [ h.span({ key: '1' }), h.span({ key: '2' }), h.span({ key: '3' }), h.div({ key: '5' }) ]; VirtualDOM.render(children1, host); VirtualDOM.render(children2, host); expect(host.children[3].nodeName.toLowerCase()).to.equal('div'); }); it('should update the text of a text node', () => { let host = document.createElement('div'); VirtualDOM.render(h.div('foo'), host); let div = host.children[0]; expect(div.textContent).to.equal('foo'); VirtualDOM.render(h.div('bar'), host); expect(host.children[0]).to.equal(div); expect(div.textContent).to.equal('bar'); }); }); }); describe('VirtualDOM with custom renderer', () => { const rendererClosure = (record: any = {}) => { return { render: (host: HTMLElement) => { const renderNode = document.createElement('div'); renderNode.className = 'p-render'; host.appendChild(renderNode); record.child = renderNode; }, unrender: (host: HTMLElement) => { host.removeChild(host.lastChild as HTMLElement); record.cleanedUp = true; } }; }; describe('realize()', () => { it('should realize successfully', () => { let node = VirtualDOM.realize(h('span', rendererClosure())); expect(node.tagName.toLowerCase()).to.equal('span'); expect(node.children[0].tagName.toLowerCase()).to.equal('div'); expect(node.children[0].className).to.equal('p-render'); }); }); describe('render()', () => { it('should render successfully at top of tree', () => { let host = document.createElement('div'); VirtualDOM.render(h('span', rendererClosure()), host); expect(host.children[0].tagName.toLowerCase()).to.equal('span'); expect(host.children[0].children[0].tagName.toLowerCase()).to.equal( 'div' ); expect(host.children[0].children[0].className).to.equal('p-render'); }); it('should render child node', () => { let host = document.createElement('div'); let record: any = { child: undefined, cleanedUp: false }; let children = [ h.a(), h.span(), h.div(h.div(), h('span', rendererClosure(record)), h.div()) ]; VirtualDOM.render(children, host); expect(host.children[2].children[1].children[0]).to.equal(record.child); expect(host.children[2].children[1].children[0].className).to.equal( 'p-render' ); }); it('should cleanup child node', () => { let host = document.createElement('div'); let record: any = { child: undefined, cleanedUp: false }; // first pass, render the custom children let children0 = [ h.a(), h.span(), h.div(h.div(), h('span', rendererClosure(record)), h.div()) ]; VirtualDOM.render(children0, host); // second pass, explicitly unrender the custom children let children1 = [h.a(), h.span(), h.label()]; VirtualDOM.render(children1, host); expect(record.cleanedUp).to.equal(true); }); }); }); }); lumino-2021.12.13/packages/virtualdom/tests/tsconfig.json000066400000000000000000000006721415564225700232430ustar00rootroot00000000000000{ "compilerOptions": { "declaration": false, "noImplicitAny": true, "noEmitOnError": true, "noUnusedLocals": true, "strictNullChecks": true, "module": "commonjs", "moduleResolution": "node", "target": "ES5", "outDir": "build", "lib": ["ES5", "DOM"], "types": ["chai", "mocha"], "rootDir": "src" }, "include": ["src/*"], "references": [ { "path": "../../algorithm" } ] } lumino-2021.12.13/packages/virtualdom/tests/webpack.config.js000066400000000000000000000003061415564225700237440ustar00rootroot00000000000000var path = require('path'); module.exports = { entry: './build/index.spec.js', mode: 'development', output: { filename: './build/bundle.test.js', path: path.resolve(__dirname) } }; lumino-2021.12.13/packages/virtualdom/tsconfig.json000066400000000000000000000010731415564225700220750ustar00rootroot00000000000000{ "compilerOptions": { "composite": true, "sourceMap": true, "declaration": true, "declarationDir": "./types", "declarationMap": true, "noImplicitAny": true, "noEmitOnError": true, "noUnusedLocals": true, "strictNullChecks": true, "module": "ES6", "moduleResolution": "node", "target": "ES5", "outDir": "lib", "lib": ["ES5", "ES2015.Collection", "DOM"], "importHelpers": true, "types": [], "rootDir": "src" }, "include": ["src/*"], "references": [ { "path": "../algorithm" } ] } lumino-2021.12.13/packages/widgets/000077500000000000000000000000001415564225700166455ustar00rootroot00000000000000lumino-2021.12.13/packages/widgets/api-extractor.json000066400000000000000000000014611415564225700223240ustar00rootroot00000000000000/** * Config file for API Extractor. For more info, please visit: https://api-extractor.com */ { "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", /** * Optionally specifies another JSON config file that this file extends from. This provides a way for * standard settings to be shared across multiple projects. * * If the path starts with "./" or "../", the path is resolved relative to the folder of the file that contains * the "extends" field. Otherwise, the first path segment is interpreted as an NPM package name, and will be * resolved using NodeJS require(). * * SUPPORTED TOKENS: none * DEFAULT VALUE: "" */ "extends": "../../api-extractor-base.json" // "extends": "my-package/include/api-extractor-base.json" } lumino-2021.12.13/packages/widgets/package.json000066400000000000000000000073451415564225700211440ustar00rootroot00000000000000{ "name": "@lumino/widgets", "version": "1.30.1", "description": "Lumino Widgets", "homepage": "https://github.com/jupyterlab/lumino", "bugs": { "url": "https://github.com/jupyterlab/lumino/issues" }, "repository": { "type": "git", "url": "https://github.com/jupyterlab/lumino.git" }, "license": "BSD-3-Clause", "author": "S. Chris Colbert ", "contributors": [ "A. Darian ", "Dave Willmer ", "S. Chris Colbert ", "Steven Silvester " ], "main": "dist/index.js", "jsdelivr": "dist/index.min.js", "unpkg": "dist/index.min.js", "module": "dist/index.es6", "types": "types/index.d.ts", "style": "style/index.css", "files": [ "dist/*", "src/*", "types/*", "style/*.css", "style/index.js" ], "scripts": { "api": "api-extractor run --local --verbose", "build": "npm run build:src && rollup -c", "build:src": "tsc --build", "build:test": "tsc --build tests && cd tests && webpack", "clean": "rimraf ./lib && rimraf *.tsbuildinfo && rimraf ./types && rimraf ./dist", "clean:test": "rimraf tests/build", "docs": "typedoc --options tdoptions.json src", "minimize": "terser dist/index.js -c -m --source-map \"content='dist/index.js.map',url='index.min.js.map'\" -o dist/index.min.js", "test": "npm run test:firefox-headless", "test:chrome": "npm run test:nobrowser -- --browsers=Chrome", "test:chrome-headless": "npm run test:nobrowser -- --browsers=ChromeHeadless", "test:debug": "npm run test:debug:firefox", "test:debug:chrome": "npm run test:debug:nobrowser -- --browsers=Chrome", "test:debug:chrome-headless": "npm run test:debug:nobrowser -- --browsers=ChromeHeadless", "test:debug:firefox": "npm run test:debug:nobrowser -- --browsers=Firefox", "test:debug:firefox-headless": "npm run test:debug:nobrowser -- --browsers=FirefoxHeadless", "test:debug:nobrowser": "cd tests && karma start --singleRun=false --debug=true --browserNoActivityTimeout=10000000 --browserDisconnectTimeout=10000000", "test:firefox": "npm run test:nobrowser -- --browsers=Firefox", "test:firefox-headless": "npm run test:nobrowser -- --browsers=FirefoxHeadless", "test:ie": "npm run test:nobrowser -- --browsers=IE", "test:nobrowser": "cd tests && karma start", "watch": "tsc --build --watch" }, "dependencies": { "@lumino/algorithm": "^1.9.1", "@lumino/commands": "^1.19.1", "@lumino/coreutils": "^1.11.1", "@lumino/disposable": "^1.10.1", "@lumino/domutils": "^1.8.1", "@lumino/dragdrop": "^1.13.1", "@lumino/keyboard": "^1.8.1", "@lumino/messaging": "^1.10.1", "@lumino/properties": "^1.8.1", "@lumino/signaling": "^1.10.1", "@lumino/virtualdom": "^1.14.1" }, "devDependencies": { "@microsoft/api-extractor": "^7.6.0", "@types/chai": "^3.4.35", "@types/mocha": "^2.2.39", "chai": "^4.3.4", "css-loader": "^3.4.0", "downlevel-dts": "^0.4.0", "es6-promise": "^4.0.5", "karma": "^6.3.4", "karma-chrome-launcher": "^3.1.0", "karma-firefox-launcher": "^2.1.1", "karma-ie-launcher": "^1.0.0", "karma-mocha": "^2.0.1", "karma-mocha-reporter": "^2.2.5", "mocha": "^9.0.3", "rimraf": "^3.0.2", "rollup": "^2.56.0", "rollup-plugin-node-resolve": "^5.2.0", "rollup-plugin-postcss": "^4.0.0", "rollup-plugin-sourcemaps": "^0.6.3", "simulate-event": "^1.4.0", "style-loader": "^1.0.2", "terser": "^5.7.1", "tslib": "^2.3.0", "typedoc": "~0.15.0", "typescript": "~3.6.0", "webpack": "^4.41.3", "webpack-cli": "^3.3.10" }, "publishConfig": { "access": "public" }, "styleModule": "style/index.js" } lumino-2021.12.13/packages/widgets/rollup.config.js000066400000000000000000000015231415564225700217650ustar00rootroot00000000000000import nodeResolve from 'rollup-plugin-node-resolve'; import sourcemaps from 'rollup-plugin-sourcemaps'; import postcss from 'rollup-plugin-postcss'; const pkg = require('./package.json'); const globals = id => id.indexOf('@lumino/') === 0 ? id.replace('@lumino/', 'lumino_') : id; export default [ { input: 'lib/index', external: id => pkg.dependencies && !!pkg.dependencies[id], output: [ { file: pkg.main, globals, format: 'umd', sourcemap: true, name: pkg.name }, { file: pkg.module + '.js', format: 'es', sourcemap: true, name: pkg.name } ], plugins: [ nodeResolve({ preferBuiltins: true }), sourcemaps(), postcss({ extensions: ['.css'], minimize: true }) ] } ]; lumino-2021.12.13/packages/widgets/src/000077500000000000000000000000001415564225700174345ustar00rootroot00000000000000lumino-2021.12.13/packages/widgets/src/accordionlayout.ts000066400000000000000000000145701415564225700232120ustar00rootroot00000000000000import { ArrayExt } from '@lumino/algorithm'; import { SplitLayout } from './splitlayout'; import { Title } from './title'; import Utils from './utils'; import { Widget } from './widget'; /** * A layout which arranges its widgets into collapsible resizable sections. */ export class AccordionLayout extends SplitLayout { /** * Construct a new accordion layout. * * @param options - The options for initializing the layout. * * #### Notes * The default orientation will be vertical. * * Titles must be rotated for horizontal accordion panel using CSS: see accordionpanel.css */ constructor(options: AccordionLayout.IOptions) { super({ ...options, orientation: options.orientation || 'vertical' }); this.titleSpace = options.titleSpace || 22; } /** * The section title height or width depending on the orientation. */ get titleSpace(): number { return this.widgetOffset; } set titleSpace(value: number) { value = Utils.clampDimension(value); if (this.widgetOffset === value) { return; } this.widgetOffset = value; if (!this.parent) { return; } this.parent.fit(); } /** * A read-only array of the section titles in the panel. */ get titles(): ReadonlyArray { return this._titles; } /** * Dispose of the resources held by the layout. */ dispose(): void { if (this.isDisposed) { return; } // Clear the layout state. this._titles.length = 0; // Dispose of the rest of the layout. super.dispose(); } /** * The renderer used by the accordion layout. */ readonly renderer: AccordionLayout.IRenderer; public updateTitle(index: number, widget: Widget): void { const oldTitle = this._titles[index]; const expanded = oldTitle.classList.contains('lm-mod-expanded'); const newTitle = Private.createTitle(this.renderer, widget.title, expanded); this._titles[index] = newTitle; // Add the title node to the parent before the widget. this.parent!.node.replaceChild(newTitle, oldTitle); } /** * Attach a widget to the parent's DOM node. * * @param index - The current index of the widget in the layout. * * @param widget - The widget to attach to the parent. */ protected attachWidget(index: number, widget: Widget): void { const title = Private.createTitle(this.renderer, widget.title); ArrayExt.insert(this._titles, index, title); // Add the title node to the parent before the widget. this.parent!.node.appendChild(title); widget.node.setAttribute('role', 'region'); widget.node.setAttribute('aria-labelledby', title.id); super.attachWidget(index, widget); } /** * Move a widget in the parent's DOM node. * * @param fromIndex - The previous index of the widget in the layout. * * @param toIndex - The current index of the widget in the layout. * * @param widget - The widget to move in the parent. */ protected moveWidget( fromIndex: number, toIndex: number, widget: Widget ): void { ArrayExt.move(this._titles, fromIndex, toIndex); super.moveWidget(fromIndex, toIndex, widget); } /** * Detach a widget from the parent's DOM node. * * @param index - The previous index of the widget in the layout. * * @param widget - The widget to detach from the parent. * * #### Notes * This is a reimplementation of the superclass method. */ protected detachWidget(index: number, widget: Widget): void { const title = ArrayExt.removeAt(this._titles, index); this.parent!.node.removeChild(title!); super.detachWidget(index, widget); } /** * Update the item position. * * @param i Item index * @param isHorizontal Whether the layout is horizontal or not * @param left Left position in pixels * @param top Top position in pixels * @param height Item height * @param width Item width * @param size Item size */ protected updateItemPosition( i: number, isHorizontal: boolean, left: number, top: number, height: number, width: number, size: number ): void { const titleStyle = this._titles[i].style; // Titles must be rotated for horizontal accordion panel using CSS: see accordionpanel.css titleStyle.top = `${top}px`; titleStyle.left = `${left}px`; titleStyle.height = `${this.widgetOffset}px`; if (isHorizontal) { titleStyle.width = `${height}px`; } else { titleStyle.width = `${width}px`; } super.updateItemPosition(i, isHorizontal, left, top, height, width, size); } private _titles: HTMLElement[] = []; } export namespace AccordionLayout { /** * A type alias for a accordion layout orientation. */ export type Orientation = SplitLayout.Orientation; /** * A type alias for a accordion layout alignment. */ export type Alignment = SplitLayout.Alignment; /** * An options object for initializing a accordion layout. */ export interface IOptions extends SplitLayout.IOptions { /** * The renderer to use for the accordion layout. */ renderer: IRenderer; /** * The section title height or width depending on the orientation. * * The default is `22`. */ titleSpace?: number; } /** * A renderer for use with an accordion layout. */ export interface IRenderer extends SplitLayout.IRenderer { /** * Common class name for all accordion titles. */ readonly titleClassName: string; /** * Render the element for a section title. * * @param data - The data to use for rendering the section title. * * @returns A element representing the section title. */ createSectionTitle(title: Title): HTMLElement; } } namespace Private { /** * Create the title HTML element. * * @param renderer Accordion renderer * @param data Widget title * @returns Title HTML element */ export function createTitle( renderer: AccordionLayout.IRenderer, data: Title, expanded: boolean = true ): HTMLElement { const title = renderer.createSectionTitle(data); title.style.position = 'absolute'; title.setAttribute('aria-label', `${data.label} Section`); title.setAttribute('aria-expanded', expanded ? 'true' : 'false'); title.setAttribute('aria-controls', data.owner.id); if (expanded) { title.classList.add('lm-mod-expanded'); } return title; } } lumino-2021.12.13/packages/widgets/src/accordionpanel.ts000066400000000000000000000237721415564225700230000ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. import { ArrayExt } from '@lumino/algorithm'; import { Message } from '@lumino/messaging'; import { AccordionLayout } from './accordionlayout'; import { SplitLayout } from './splitlayout'; import { SplitPanel } from './splitpanel'; import { Title } from './title'; import { Widget } from './widget'; /** * A panel which arranges its widgets into resizable sections separated by a title widget. * * #### Notes * This class provides a convenience wrapper around [[AccordionLayout]]. */ export class AccordionPanel extends SplitPanel { /** * Construct a new accordion panel. * * @param options - The options for initializing the accordion panel. */ constructor(options: AccordionPanel.IOptions = {}) { super({ ...options, layout: Private.createLayout(options) }); this.addClass('lm-AccordionPanel'); } /** * The renderer used by the accordion panel. */ get renderer(): AccordionPanel.IRenderer { return (this.layout as AccordionLayout).renderer; } /** * The section title space. * * This is the height if the panel is vertical and the width if it is * horizontal. */ get titleSpace(): number { return (this.layout as AccordionLayout).titleSpace; } set titleSpace(value: number) { (this.layout as AccordionLayout).titleSpace = value; } /** * A read-only array of the section titles in the panel. */ get titles(): ReadonlyArray { return (this.layout as AccordionLayout).titles; } /** * Add a widget to the end of the panel. * * @param widget - The widget to add to the panel. * * #### Notes * If the widget is already contained in the panel, it will be moved. */ addWidget(widget: Widget): void { super.addWidget(widget); widget.title.changed.connect(this._onTitleChanged, this); } /** * Insert a widget at the specified index. * * @param index - The index at which to insert the widget. * * @param widget - The widget to insert into to the panel. * * #### Notes * If the widget is already contained in the panel, it will be moved. */ insertWidget(index: number, widget: Widget): void { super.insertWidget(index, widget); widget.title.changed.connect(this._onTitleChanged, this); } /** * Handle the DOM events for the accordion panel. * * @param event - The DOM event sent to the panel. * * #### Notes * This method implements the DOM `EventListener` interface and is * called in response to events on the panel's DOM node. It should * not be called directly by user code. */ handleEvent(event: Event): void { super.handleEvent(event); switch (event.type) { case 'click': this._evtClick(event as MouseEvent); break; case 'keydown': this._eventKeyDown(event as KeyboardEvent); break; } } /** * A message handler invoked on a `'before-attach'` message. */ protected onBeforeAttach(msg: Message): void { this.node.addEventListener('click', this); this.node.addEventListener('keydown', this); super.onBeforeAttach(msg); } /** * A message handler invoked on an `'after-detach'` message. */ protected onAfterDetach(msg: Message): void { super.onAfterDetach(msg); this.node.removeEventListener('click', this); this.node.removeEventListener('keydown', this); } /** * Handle the `changed` signal of a title object. */ private _onTitleChanged(sender: Title): void { const index = ArrayExt.findFirstIndex(this.widgets, widget => { return widget.contains(sender.owner); }); if (index >= 0) { (this.layout as AccordionLayout).updateTitle(index, sender.owner); this.update(); } } /** * Handle the `'click'` event for the accordion panel */ private _evtClick(event: MouseEvent): void { const target = event.target as HTMLElement | null; if (target) { const index = ArrayExt.findFirstIndex(this.titles, title => { return title.contains(target); }); if (index >= 0) { event.preventDefault(); event.stopPropagation(); const title = this.titles[index]; const widget = (this.layout as AccordionLayout).widgets[index]; if (widget.isHidden) { title.classList.add('lm-mod-expanded'); title.setAttribute('aria-expanded', 'true'); widget.show(); } else { title.classList.remove('lm-mod-expanded'); title.setAttribute('aria-expanded', 'false'); widget.hide(); } } } } /** * Handle the `'keydown'` event for the accordion panel. */ private _eventKeyDown(event: KeyboardEvent): void { if (event.defaultPrevented) { return; } const target = event.target as HTMLElement | null; let handled = false; if (target) { const index = ArrayExt.findFirstIndex(this.titles, title => { return title.contains(target); }); if (index >= 0) { const keyCode = event.keyCode.toString(); // If Space or Enter is pressed on title, emulate click event if (event.key.match(/Space|Enter/) || keyCode.match(/13|32/)) { target.click(); handled = true; } else if ( this.orientation === 'horizontal' ? event.key.match(/ArrowLeft|ArrowRight/) || keyCode.match(/37|39/) : event.key.match(/ArrowUp|ArrowDown/) || keyCode.match(/38|40/) ) { // If Up or Down (for vertical) / Left or Right (for horizontal) is pressed on title, loop on titles const direction = event.key.match(/ArrowLeft|ArrowUp/) || keyCode.match(/37|38/) ? -1 : 1; const length = this.titles.length; const newIndex = (index + length + direction) % length; this.titles[newIndex].focus(); handled = true; } else if (event.key === 'End' || keyCode === '35') { // If End is pressed on title, focus on the last title this.titles[this.titles.length - 1].focus(); handled = true; } else if (event.key === 'Home' || keyCode === '36') { // If Home is pressed on title, focus on the first title this.titles[0].focus(); handled = true; } } if (handled) { event.preventDefault(); } } } } /** * The namespace for the `AccordionPanel` class statics. */ export namespace AccordionPanel { /** * A type alias for a accordion panel orientation. */ export type Orientation = SplitLayout.Orientation; /** * A type alias for a accordion panel alignment. */ export type Alignment = SplitLayout.Alignment; /** * A type alias for a accordion panel renderer. */ export type IRenderer = AccordionLayout.IRenderer; /** * An options object for initializing a accordion panel. */ export interface IOptions extends Partial { /** * The accordion layout to use for the accordion panel. * * If this is provided, the other options are ignored. * * The default is a new `AccordionLayout`. */ layout?: AccordionLayout; } /** * The default implementation of `IRenderer`. */ export class Renderer extends SplitPanel.Renderer implements IRenderer { /** * A selector which matches any title node in the accordion. */ readonly titleClassName = 'lm-AccordionPanel-title'; /** * Render the collapse indicator for a section title. * * @param data - The data to use for rendering the section title. * * @returns A element representing the collapse indicator. */ createCollapseIcon(data: Title): HTMLElement { return document.createElement('span'); } /** * Render the element for a section title. * * @param data - The data to use for rendering the section title. * * @returns A element representing the section title. */ createSectionTitle(data: Title): HTMLElement { const handle = document.createElement('h3'); handle.setAttribute('role', 'button'); handle.setAttribute('tabindex', '0'); handle.id = this.createTitleKey(data); handle.className = this.titleClassName; handle.title = data.caption; for (const aData in data.dataset) { handle.dataset[aData] = data.dataset[aData]; } const collapser = handle.appendChild(this.createCollapseIcon(data)); collapser.className = 'lm-AccordionPanel-titleCollapser'; const label = handle.appendChild(document.createElement('span')); label.className = 'lm-AccordionPanel-titleLabel'; label.textContent = data.label; return handle; } /** * Create a unique render key for the title. * * @param data - The data to use for the title. * * @returns The unique render key for the title. * * #### Notes * This method caches the key against the section title the first time * the key is generated. */ createTitleKey(data: Title): string { let key = this._titleKeys.get(data); if (key === undefined) { key = `title-key-${this._titleID++}`; this._titleKeys.set(data, key); } return key; } private _titleID = 0; private _titleKeys = new WeakMap, string>(); } /** * The default `Renderer` instance. */ export const defaultRenderer = new Renderer(); } namespace Private { /** * Create an accordion layout for the given panel options. * * @param options Panel options * @returns Panel layout */ export function createLayout( options: AccordionPanel.IOptions ): AccordionLayout { return ( options.layout || new AccordionLayout({ renderer: options.renderer || AccordionPanel.defaultRenderer, orientation: options.orientation, alignment: options.alignment, spacing: options.spacing, titleSpace: options.titleSpace }) ); } } lumino-2021.12.13/packages/widgets/src/boxengine.ts000066400000000000000000000374771415564225700220040ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ /** * A sizer object for use with the box engine layout functions. * * #### Notes * A box sizer holds the geometry information for an object along an * arbitrary layout orientation. * * For best performance, this class should be treated as a raw data * struct. It should not typically be subclassed. */ export class BoxSizer { /** * The preferred size for the sizer. * * #### Notes * The sizer will be given this initial size subject to its size * bounds. The sizer will not deviate from this size unless such * deviation is required to fit into the available layout space. * * There is no limit to this value, but it will be clamped to the * bounds defined by [[minSize]] and [[maxSize]]. * * The default value is `0`. */ sizeHint = 0; /** * The minimum size of the sizer. * * #### Notes * The sizer will never be sized less than this value, even if * it means the sizer will overflow the available layout space. * * It is assumed that this value lies in the range `[0, Infinity)` * and that it is `<=` to [[maxSize]]. Failure to adhere to this * constraint will yield undefined results. * * The default value is `0`. */ minSize = 0; /** * The maximum size of the sizer. * * #### Notes * The sizer will never be sized greater than this value, even if * it means the sizer will underflow the available layout space. * * It is assumed that this value lies in the range `[0, Infinity]` * and that it is `>=` to [[minSize]]. Failure to adhere to this * constraint will yield undefined results. * * The default value is `Infinity`. */ maxSize = Infinity; /** * The stretch factor for the sizer. * * #### Notes * This controls how much the sizer stretches relative to its sibling * sizers when layout space is distributed. A stretch factor of zero * is special and will cause the sizer to only be resized after all * other sizers with a stretch factor greater than zero have been * resized to their limits. * * It is assumed that this value is an integer that lies in the range * `[0, Infinity)`. Failure to adhere to this constraint will yield * undefined results. * * The default value is `1`. */ stretch = 1; /** * The computed size of the sizer. * * #### Notes * This value is the output of a call to [[boxCalc]]. It represents * the computed size for the object along the layout orientation, * and will always lie in the range `[minSize, maxSize]`. * * This value is output only. * * Changing this value will have no effect. */ size = 0; /** * An internal storage property for the layout algorithm. * * #### Notes * This value is used as temporary storage by the layout algorithm. * * Changing this value will have no effect. */ done = false; } /** * The namespace for the box engine layout functions. */ export namespace BoxEngine { /** * Calculate the optimal layout sizes for a sequence of box sizers. * * This distributes the available layout space among the box sizers * according to the following algorithm: * * 1. Initialize the sizers's size to its size hint and compute the * sums for each of size hint, min size, and max size. * * 2. If the total size hint equals the available space, return. * * 3. If the available space is less than the total min size, set all * sizers to their min size and return. * * 4. If the available space is greater than the total max size, set * all sizers to their max size and return. * * 5. If the layout space is less than the total size hint, distribute * the negative delta as follows: * * a. Shrink each sizer with a stretch factor greater than zero by * an amount proportional to the negative space and the sum of * stretch factors. If the sizer reaches its min size, remove * it and its stretch factor from the computation. * * b. If after adjusting all stretch sizers there remains negative * space, distribute the space equally among the sizers with a * stretch factor of zero. If a sizer reaches its min size, * remove it from the computation. * * 6. If the layout space is greater than the total size hint, * distribute the positive delta as follows: * * a. Expand each sizer with a stretch factor greater than zero by * an amount proportional to the postive space and the sum of * stretch factors. If the sizer reaches its max size, remove * it and its stretch factor from the computation. * * b. If after adjusting all stretch sizers there remains positive * space, distribute the space equally among the sizers with a * stretch factor of zero. If a sizer reaches its max size, * remove it from the computation. * * 7. return * * @param sizers - The sizers for a particular layout line. * * @param space - The available layout space for the sizers. * * @returns The delta between the provided available space and the * actual consumed space. This value will be zero if the sizers * can be adjusted to fit, negative if the available space is too * small, and positive if the available space is too large. * * #### Notes * The [[size]] of each sizer is updated with the computed size. * * This function can be called at any time to recompute the layout for * an existing sequence of sizers. The previously computed results will * have no effect on the new output. It is therefore not necessary to * create new sizer objects on each resize event. */ export function calc(sizers: ArrayLike, space: number): number { // Bail early if there is nothing to do. let count = sizers.length; if (count === 0) { return space; } // Setup the size and stretch counters. let totalMin = 0; let totalMax = 0; let totalSize = 0; let totalStretch = 0; let stretchCount = 0; // Setup the sizers and compute the totals. for (let i = 0; i < count; ++i) { let sizer = sizers[i]; let min = sizer.minSize; let max = sizer.maxSize; let hint = sizer.sizeHint; sizer.done = false; sizer.size = Math.max(min, Math.min(hint, max)); totalSize += sizer.size; totalMin += min; totalMax += max; if (sizer.stretch > 0) { totalStretch += sizer.stretch; stretchCount++; } } // If the space is equal to the total size, return early. if (space === totalSize) { return 0; } // If the space is less than the total min, minimize each sizer. if (space <= totalMin) { for (let i = 0; i < count; ++i) { let sizer = sizers[i]; sizer.size = sizer.minSize; } return space - totalMin; } // If the space is greater than the total max, maximize each sizer. if (space >= totalMax) { for (let i = 0; i < count; ++i) { let sizer = sizers[i]; sizer.size = sizer.maxSize; } return space - totalMax; } // The loops below perform sub-pixel precision sizing. A near zero // value is used for compares instead of zero to ensure that the // loop terminates when the subdivided space is reasonably small. let nearZero = 0.01; // A counter which is decremented each time a sizer is resized to // its limit. This ensures the loops terminate even if there is // space remaining to distribute. let notDoneCount = count; // Distribute negative delta space. if (space < totalSize) { // Shrink each stretchable sizer by an amount proportional to its // stretch factor. If a sizer reaches its min size it's marked as // done. The loop progresses in phases where each sizer is given // a chance to consume its fair share for the pass, regardless of // whether a sizer before it reached its limit. This continues // until the stretchable sizers or the free space is exhausted. let freeSpace = totalSize - space; while (stretchCount > 0 && freeSpace > nearZero) { let distSpace = freeSpace; let distStretch = totalStretch; for (let i = 0; i < count; ++i) { let sizer = sizers[i]; if (sizer.done || sizer.stretch === 0) { continue; } let amt = (sizer.stretch * distSpace) / distStretch; if (sizer.size - amt <= sizer.minSize) { freeSpace -= sizer.size - sizer.minSize; totalStretch -= sizer.stretch; sizer.size = sizer.minSize; sizer.done = true; notDoneCount--; stretchCount--; } else { freeSpace -= amt; sizer.size -= amt; } } } // Distribute any remaining space evenly among the non-stretchable // sizers. This progresses in phases in the same manner as above. while (notDoneCount > 0 && freeSpace > nearZero) { let amt = freeSpace / notDoneCount; for (let i = 0; i < count; ++i) { let sizer = sizers[i]; if (sizer.done) { continue; } if (sizer.size - amt <= sizer.minSize) { freeSpace -= sizer.size - sizer.minSize; sizer.size = sizer.minSize; sizer.done = true; notDoneCount--; } else { freeSpace -= amt; sizer.size -= amt; } } } } // Distribute positive delta space. else { // Expand each stretchable sizer by an amount proportional to its // stretch factor. If a sizer reaches its max size it's marked as // done. The loop progresses in phases where each sizer is given // a chance to consume its fair share for the pass, regardless of // whether a sizer before it reached its limit. This continues // until the stretchable sizers or the free space is exhausted. let freeSpace = space - totalSize; while (stretchCount > 0 && freeSpace > nearZero) { let distSpace = freeSpace; let distStretch = totalStretch; for (let i = 0; i < count; ++i) { let sizer = sizers[i]; if (sizer.done || sizer.stretch === 0) { continue; } let amt = (sizer.stretch * distSpace) / distStretch; if (sizer.size + amt >= sizer.maxSize) { freeSpace -= sizer.maxSize - sizer.size; totalStretch -= sizer.stretch; sizer.size = sizer.maxSize; sizer.done = true; notDoneCount--; stretchCount--; } else { freeSpace -= amt; sizer.size += amt; } } } // Distribute any remaining space evenly among the non-stretchable // sizers. This progresses in phases in the same manner as above. while (notDoneCount > 0 && freeSpace > nearZero) { let amt = freeSpace / notDoneCount; for (let i = 0; i < count; ++i) { let sizer = sizers[i]; if (sizer.done) { continue; } if (sizer.size + amt >= sizer.maxSize) { freeSpace -= sizer.maxSize - sizer.size; sizer.size = sizer.maxSize; sizer.done = true; notDoneCount--; } else { freeSpace -= amt; sizer.size += amt; } } } } // Indicate that the consumed space equals the available space. return 0; } /** * Adjust a sizer by a delta and update its neighbors accordingly. * * @param sizers - The sizers which should be adjusted. * * @param index - The index of the sizer to grow. * * @param delta - The amount to adjust the sizer, positive or negative. * * #### Notes * This will adjust the indicated sizer by the specified amount, along * with the sizes of the appropriate neighbors, subject to the limits * specified by each of the sizers. * * This is useful when implementing box layouts where the boundaries * between the sizers are interactively adjustable by the user. */ export function adjust( sizers: ArrayLike, index: number, delta: number ): void { // Bail early when there is nothing to do. if (sizers.length === 0 || delta === 0) { return; } // Dispatch to the proper implementation. if (delta > 0) { growSizer(sizers, index, delta); } else { shrinkSizer(sizers, index, -delta); } } /** * Grow a sizer by a positive delta and adjust neighbors. */ function growSizer( sizers: ArrayLike, index: number, delta: number ): void { // Compute how much the items to the left can expand. let growLimit = 0; for (let i = 0; i <= index; ++i) { let sizer = sizers[i]; growLimit += sizer.maxSize - sizer.size; } // Compute how much the items to the right can shrink. let shrinkLimit = 0; for (let i = index + 1, n = sizers.length; i < n; ++i) { let sizer = sizers[i]; shrinkLimit += sizer.size - sizer.minSize; } // Clamp the delta adjustment to the limits. delta = Math.min(delta, growLimit, shrinkLimit); // Grow the sizers to the left by the delta. let grow = delta; for (let i = index; i >= 0 && grow > 0; --i) { let sizer = sizers[i]; let limit = sizer.maxSize - sizer.size; if (limit >= grow) { sizer.sizeHint = sizer.size + grow; grow = 0; } else { sizer.sizeHint = sizer.size + limit; grow -= limit; } } // Shrink the sizers to the right by the delta. let shrink = delta; for (let i = index + 1, n = sizers.length; i < n && shrink > 0; ++i) { let sizer = sizers[i]; let limit = sizer.size - sizer.minSize; if (limit >= shrink) { sizer.sizeHint = sizer.size - shrink; shrink = 0; } else { sizer.sizeHint = sizer.size - limit; shrink -= limit; } } } /** * Shrink a sizer by a positive delta and adjust neighbors. */ function shrinkSizer( sizers: ArrayLike, index: number, delta: number ): void { // Compute how much the items to the right can expand. let growLimit = 0; for (let i = index + 1, n = sizers.length; i < n; ++i) { let sizer = sizers[i]; growLimit += sizer.maxSize - sizer.size; } // Compute how much the items to the left can shrink. let shrinkLimit = 0; for (let i = 0; i <= index; ++i) { let sizer = sizers[i]; shrinkLimit += sizer.size - sizer.minSize; } // Clamp the delta adjustment to the limits. delta = Math.min(delta, growLimit, shrinkLimit); // Grow the sizers to the right by the delta. let grow = delta; for (let i = index + 1, n = sizers.length; i < n && grow > 0; ++i) { let sizer = sizers[i]; let limit = sizer.maxSize - sizer.size; if (limit >= grow) { sizer.sizeHint = sizer.size + grow; grow = 0; } else { sizer.sizeHint = sizer.size + limit; grow -= limit; } } // Shrink the sizers to the left by the delta. let shrink = delta; for (let i = index; i >= 0 && shrink > 0; --i) { let sizer = sizers[i]; let limit = sizer.size - sizer.minSize; if (limit >= shrink) { sizer.sizeHint = sizer.size - shrink; shrink = 0; } else { sizer.sizeHint = sizer.size - limit; shrink -= limit; } } } } lumino-2021.12.13/packages/widgets/src/boxlayout.ts000066400000000000000000000426321415564225700220410ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { ArrayExt, each } from '@lumino/algorithm'; import { ElementExt } from '@lumino/domutils'; import { Message, MessageLoop } from '@lumino/messaging'; import { AttachedProperty } from '@lumino/properties'; import { BoxEngine, BoxSizer } from './boxengine'; import { LayoutItem } from './layout'; import { PanelLayout } from './panellayout'; import Utils from './utils'; import { Widget } from './widget'; /** * A layout which arranges its widgets in a single row or column. */ export class BoxLayout extends PanelLayout { /** * Construct a new box layout. * * @param options - The options for initializing the layout. */ constructor(options: BoxLayout.IOptions = {}) { super(); if (options.direction !== undefined) { this._direction = options.direction; } if (options.alignment !== undefined) { this._alignment = options.alignment; } if (options.spacing !== undefined) { this._spacing = Utils.clampDimension(options.spacing); } } /** * Dispose of the resources held by the layout. */ dispose(): void { // Dispose of the layout items. each(this._items, item => { item.dispose(); }); // Clear the layout state. this._box = null; this._items.length = 0; this._sizers.length = 0; // Dispose of the rest of the layout. super.dispose(); } /** * Get the layout direction for the box layout. */ get direction(): BoxLayout.Direction { return this._direction; } /** * Set the layout direction for the box layout. */ set direction(value: BoxLayout.Direction) { if (this._direction === value) { return; } this._direction = value; if (!this.parent) { return; } this.parent.dataset['direction'] = value; this.parent.fit(); } /** * Get the content alignment for the box layout. * * #### Notes * This is the alignment of the widgets in the layout direction. * * The alignment has no effect if the widgets can expand to fill the * entire box layout. */ get alignment(): BoxLayout.Alignment { return this._alignment; } /** * Set the content alignment for the box layout. * * #### Notes * This is the alignment of the widgets in the layout direction. * * The alignment has no effect if the widgets can expand to fill the * entire box layout. */ set alignment(value: BoxLayout.Alignment) { if (this._alignment === value) { return; } this._alignment = value; if (!this.parent) { return; } this.parent.dataset['alignment'] = value; this.parent.update(); } /** * Get the inter-element spacing for the box layout. */ get spacing(): number { return this._spacing; } /** * Set the inter-element spacing for the box layout. */ set spacing(value: number) { value = Utils.clampDimension(value); if (this._spacing === value) { return; } this._spacing = value; if (!this.parent) { return; } this.parent.fit(); } /** * Perform layout initialization which requires the parent widget. */ protected init(): void { this.parent!.dataset['direction'] = this.direction; this.parent!.dataset['alignment'] = this.alignment; super.init(); } /** * Attach a widget to the parent's DOM node. * * @param index - The current index of the widget in the layout. * * @param widget - The widget to attach to the parent. * * #### Notes * This is a reimplementation of the superclass method. */ protected attachWidget(index: number, widget: Widget): void { // Create and add a new layout item for the widget. ArrayExt.insert(this._items, index, new LayoutItem(widget)); // Create and add a new sizer for the widget. ArrayExt.insert(this._sizers, index, new BoxSizer()); // Send a `'before-attach'` message if the parent is attached. if (this.parent!.isAttached) { MessageLoop.sendMessage(widget, Widget.Msg.BeforeAttach); } // Add the widget's node to the parent. this.parent!.node.appendChild(widget.node); // Send an `'after-attach'` message if the parent is attached. if (this.parent!.isAttached) { MessageLoop.sendMessage(widget, Widget.Msg.AfterAttach); } // Post a fit request for the parent widget. this.parent!.fit(); } /** * Move a widget in the parent's DOM node. * * @param fromIndex - The previous index of the widget in the layout. * * @param toIndex - The current index of the widget in the layout. * * @param widget - The widget to move in the parent. * * #### Notes * This is a reimplementation of the superclass method. */ protected moveWidget( fromIndex: number, toIndex: number, widget: Widget ): void { // Move the layout item for the widget. ArrayExt.move(this._items, fromIndex, toIndex); // Move the sizer for the widget. ArrayExt.move(this._sizers, fromIndex, toIndex); // Post an update request for the parent widget. this.parent!.update(); } /** * Detach a widget from the parent's DOM node. * * @param index - The previous index of the widget in the layout. * * @param widget - The widget to detach from the parent. * * #### Notes * This is a reimplementation of the superclass method. */ protected detachWidget(index: number, widget: Widget): void { // Remove the layout item for the widget. let item = ArrayExt.removeAt(this._items, index); // Remove the sizer for the widget. ArrayExt.removeAt(this._sizers, index); // Send a `'before-detach'` message if the parent is attached. if (this.parent!.isAttached) { MessageLoop.sendMessage(widget, Widget.Msg.BeforeDetach); } // Remove the widget's node from the parent. this.parent!.node.removeChild(widget.node); // Send an `'after-detach'` message if the parent is attached. if (this.parent!.isAttached) { MessageLoop.sendMessage(widget, Widget.Msg.AfterDetach); } // Dispose of the layout item. item!.dispose(); // Post a fit request for the parent widget. this.parent!.fit(); } /** * A message handler invoked on a `'before-show'` message. */ protected onBeforeShow(msg: Message): void { super.onBeforeShow(msg); this.parent!.update(); } /** * A message handler invoked on a `'before-attach'` message. */ protected onBeforeAttach(msg: Message): void { super.onBeforeAttach(msg); this.parent!.fit(); } /** * A message handler invoked on a `'child-shown'` message. */ protected onChildShown(msg: Widget.ChildMessage): void { this.parent!.fit(); } /** * A message handler invoked on a `'child-hidden'` message. */ protected onChildHidden(msg: Widget.ChildMessage): void { this.parent!.fit(); } /** * A message handler invoked on a `'resize'` message. */ protected onResize(msg: Widget.ResizeMessage): void { if (this.parent!.isVisible) { this._update(msg.width, msg.height); } } /** * A message handler invoked on an `'update-request'` message. */ protected onUpdateRequest(msg: Message): void { if (this.parent!.isVisible) { this._update(-1, -1); } } /** * A message handler invoked on a `'fit-request'` message. */ protected onFitRequest(msg: Message): void { if (this.parent!.isAttached) { this._fit(); } } /** * Fit the layout to the total size required by the widgets. */ private _fit(): void { // Compute the visible item count. let nVisible = 0; for (let i = 0, n = this._items.length; i < n; ++i) { nVisible += +!this._items[i].isHidden; } // Update the fixed space for the visible items. this._fixed = this._spacing * Math.max(0, nVisible - 1); // Setup the computed minimum size. let horz = Private.isHorizontal(this._direction); let minW = horz ? this._fixed : 0; let minH = horz ? 0 : this._fixed; // Update the sizers and computed minimum size. for (let i = 0, n = this._items.length; i < n; ++i) { // Fetch the item and corresponding box sizer. let item = this._items[i]; let sizer = this._sizers[i]; // If the item is hidden, it should consume zero size. if (item.isHidden) { sizer.minSize = 0; sizer.maxSize = 0; continue; } // Update the size limits for the item. item.fit(); // Update the size basis and stretch factor. sizer.sizeHint = BoxLayout.getSizeBasis(item.widget); sizer.stretch = BoxLayout.getStretch(item.widget); // Update the sizer limits and computed min size. if (horz) { sizer.minSize = item.minWidth; sizer.maxSize = item.maxWidth; minW += item.minWidth; minH = Math.max(minH, item.minHeight); } else { sizer.minSize = item.minHeight; sizer.maxSize = item.maxHeight; minH += item.minHeight; minW = Math.max(minW, item.minWidth); } } // Update the box sizing and add it to the computed min size. let box = (this._box = ElementExt.boxSizing(this.parent!.node)); minW += box.horizontalSum; minH += box.verticalSum; // Update the parent's min size constraints. let style = this.parent!.node.style; style.minWidth = `${minW}px`; style.minHeight = `${minH}px`; // Set the dirty flag to ensure only a single update occurs. this._dirty = true; // Notify the ancestor that it should fit immediately. This may // cause a resize of the parent, fulfilling the required update. if (this.parent!.parent) { MessageLoop.sendMessage(this.parent!.parent!, Widget.Msg.FitRequest); } // If the dirty flag is still set, the parent was not resized. // Trigger the required update on the parent widget immediately. if (this._dirty) { MessageLoop.sendMessage(this.parent!, Widget.Msg.UpdateRequest); } } /** * Update the layout position and size of the widgets. * * The parent offset dimensions should be `-1` if unknown. */ private _update(offsetWidth: number, offsetHeight: number): void { // Clear the dirty flag to indicate the update occurred. this._dirty = false; // Compute the visible item count. let nVisible = 0; for (let i = 0, n = this._items.length; i < n; ++i) { nVisible += +!this._items[i].isHidden; } // Bail early if there are no visible items to layout. if (nVisible === 0) { return; } // Measure the parent if the offset dimensions are unknown. if (offsetWidth < 0) { offsetWidth = this.parent!.node.offsetWidth; } if (offsetHeight < 0) { offsetHeight = this.parent!.node.offsetHeight; } // Ensure the parent box sizing data is computed. if (!this._box) { this._box = ElementExt.boxSizing(this.parent!.node); } // Compute the layout area adjusted for border and padding. let top = this._box.paddingTop; let left = this._box.paddingLeft; let width = offsetWidth - this._box.horizontalSum; let height = offsetHeight - this._box.verticalSum; // Distribute the layout space and adjust the start position. let delta: number; switch (this._direction) { case 'left-to-right': delta = BoxEngine.calc(this._sizers, Math.max(0, width - this._fixed)); break; case 'top-to-bottom': delta = BoxEngine.calc(this._sizers, Math.max(0, height - this._fixed)); break; case 'right-to-left': delta = BoxEngine.calc(this._sizers, Math.max(0, width - this._fixed)); left += width; break; case 'bottom-to-top': delta = BoxEngine.calc(this._sizers, Math.max(0, height - this._fixed)); top += height; break; default: throw 'unreachable'; } // Setup the variables for justification and alignment offset. let extra = 0; let offset = 0; // Account for alignment if there is extra layout space. if (delta > 0) { switch (this._alignment) { case 'start': break; case 'center': extra = 0; offset = delta / 2; break; case 'end': extra = 0; offset = delta; break; case 'justify': extra = delta / nVisible; offset = 0; break; default: throw 'unreachable'; } } // Layout the items using the computed box sizes. for (let i = 0, n = this._items.length; i < n; ++i) { // Fetch the item. let item = this._items[i]; // Ignore hidden items. if (item.isHidden) { continue; } // Fetch the computed size for the widget. let size = this._sizers[i].size; // Update the widget geometry and advance the relevant edge. switch (this._direction) { case 'left-to-right': item.update(left + offset, top, size + extra, height); left += size + extra + this._spacing; break; case 'top-to-bottom': item.update(left, top + offset, width, size + extra); top += size + extra + this._spacing; break; case 'right-to-left': item.update(left - offset - size - extra, top, size + extra, height); left -= size + extra + this._spacing; break; case 'bottom-to-top': item.update(left, top - offset - size - extra, width, size + extra); top -= size + extra + this._spacing; break; default: throw 'unreachable'; } } } private _fixed = 0; private _spacing = 4; private _dirty = false; private _sizers: BoxSizer[] = []; private _items: LayoutItem[] = []; private _box: ElementExt.IBoxSizing | null = null; private _alignment: BoxLayout.Alignment = 'start'; private _direction: BoxLayout.Direction = 'top-to-bottom'; } /** * The namespace for the `BoxLayout` class statics. */ export namespace BoxLayout { /** * A type alias for a box layout direction. */ export type Direction = | 'left-to-right' | 'right-to-left' | 'top-to-bottom' | 'bottom-to-top'; /** * A type alias for a box layout alignment. */ export type Alignment = 'start' | 'center' | 'end' | 'justify'; /** * An options object for initializing a box layout. */ export interface IOptions { /** * The direction of the layout. * * The default is `'top-to-bottom'`. */ direction?: Direction; /** * The content alignment of the layout. * * The default is `'start'`. */ alignment?: Alignment; /** * The spacing between items in the layout. * * The default is `4`. */ spacing?: number; } /** * Get the box layout stretch factor for the given widget. * * @param widget - The widget of interest. * * @returns The box layout stretch factor for the widget. */ export function getStretch(widget: Widget): number { return Private.stretchProperty.get(widget); } /** * Set the box layout stretch factor for the given widget. * * @param widget - The widget of interest. * * @param value - The value for the stretch factor. */ export function setStretch(widget: Widget, value: number): void { Private.stretchProperty.set(widget, value); } /** * Get the box layout size basis for the given widget. * * @param widget - The widget of interest. * * @returns The box layout size basis for the widget. */ export function getSizeBasis(widget: Widget): number { return Private.sizeBasisProperty.get(widget); } /** * Set the box layout size basis for the given widget. * * @param widget - The widget of interest. * * @param value - The value for the size basis. */ export function setSizeBasis(widget: Widget, value: number): void { Private.sizeBasisProperty.set(widget, value); } } /** * The namespace for the module implementation details. */ namespace Private { /** * The property descriptor for a widget stretch factor. */ export const stretchProperty = new AttachedProperty({ name: 'stretch', create: () => 0, coerce: (owner, value) => Math.max(0, Math.floor(value)), changed: onChildSizingChanged }); /** * The property descriptor for a widget size basis. */ export const sizeBasisProperty = new AttachedProperty({ name: 'sizeBasis', create: () => 0, coerce: (owner, value) => Math.max(0, Math.floor(value)), changed: onChildSizingChanged }); /** * Test whether a direction has horizontal orientation. */ export function isHorizontal(dir: BoxLayout.Direction): boolean { return dir === 'left-to-right' || dir === 'right-to-left'; } /** * Clamp a spacing value to an integer >= 0. */ export function clampSpacing(value: number): number { return Math.max(0, Math.floor(value)); } /** * The change handler for the attached sizing properties. */ function onChildSizingChanged(child: Widget): void { if (child.parent && child.parent.layout instanceof BoxLayout) { child.parent.fit(); } } } lumino-2021.12.13/packages/widgets/src/boxpanel.ts000066400000000000000000000124031415564225700216140ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { BoxLayout } from './boxlayout'; import { Panel } from './panel'; import { Widget } from './widget'; /** * A panel which arranges its widgets in a single row or column. * * #### Notes * This class provides a convenience wrapper around a [[BoxLayout]]. */ export class BoxPanel extends Panel { /** * Construct a new box panel. * * @param options - The options for initializing the box panel. */ constructor(options: BoxPanel.IOptions = {}) { super({ layout: Private.createLayout(options) }); this.addClass('lm-BoxPanel'); /* */ this.addClass('p-BoxPanel'); /* */ } /** * Get the layout direction for the box panel. */ get direction(): BoxPanel.Direction { return (this.layout as BoxLayout).direction; } /** * Set the layout direction for the box panel. */ set direction(value: BoxPanel.Direction) { (this.layout as BoxLayout).direction = value; } /** * Get the content alignment for the box panel. * * #### Notes * This is the alignment of the widgets in the layout direction. * * The alignment has no effect if the widgets can expand to fill the * entire box layout. */ get alignment(): BoxPanel.Alignment { return (this.layout as BoxLayout).alignment; } /** * Set the content alignment for the box panel. * * #### Notes * This is the alignment of the widgets in the layout direction. * * The alignment has no effect if the widgets can expand to fill the * entire box layout. */ set alignment(value: BoxPanel.Alignment) { (this.layout as BoxLayout).alignment = value; } /** * Get the inter-element spacing for the box panel. */ get spacing(): number { return (this.layout as BoxLayout).spacing; } /** * Set the inter-element spacing for the box panel. */ set spacing(value: number) { (this.layout as BoxLayout).spacing = value; } /** * A message handler invoked on a `'child-added'` message. */ protected onChildAdded(msg: Widget.ChildMessage): void { msg.child.addClass('lm-BoxPanel-child'); /* */ msg.child.addClass('p-BoxPanel-child'); /* */ } /** * A message handler invoked on a `'child-removed'` message. */ protected onChildRemoved(msg: Widget.ChildMessage): void { msg.child.removeClass('lm-BoxPanel-child'); /* */ msg.child.removeClass('p-BoxPanel-child'); /* */ } } /** * The namespace for the `BoxPanel` class statics. */ export namespace BoxPanel { /** * A type alias for a box panel direction. */ export type Direction = BoxLayout.Direction; /** * A type alias for a box panel alignment. */ export type Alignment = BoxLayout.Alignment; /** * An options object for initializing a box panel. */ export interface IOptions { /** * The layout direction of the panel. * * The default is `'top-to-bottom'`. */ direction?: Direction; /** * The content alignment of the panel. * * The default is `'start'`. */ alignment?: Alignment; /** * The spacing between items in the panel. * * The default is `4`. */ spacing?: number; /** * The box layout to use for the box panel. * * If this is provided, the other options are ignored. * * The default is a new `BoxLayout`. */ layout?: BoxLayout; } /** * Get the box panel stretch factor for the given widget. * * @param widget - The widget of interest. * * @returns The box panel stretch factor for the widget. */ export function getStretch(widget: Widget): number { return BoxLayout.getStretch(widget); } /** * Set the box panel stretch factor for the given widget. * * @param widget - The widget of interest. * * @param value - The value for the stretch factor. */ export function setStretch(widget: Widget, value: number): void { BoxLayout.setStretch(widget, value); } /** * Get the box panel size basis for the given widget. * * @param widget - The widget of interest. * * @returns The box panel size basis for the widget. */ export function getSizeBasis(widget: Widget): number { return BoxLayout.getSizeBasis(widget); } /** * Set the box panel size basis for the given widget. * * @param widget - The widget of interest. * * @param value - The value for the size basis. */ export function setSizeBasis(widget: Widget, value: number): void { BoxLayout.setSizeBasis(widget, value); } } /** * The namespace for the module implementation details. */ namespace Private { /** * Create a box layout for the given panel options. */ export function createLayout(options: BoxPanel.IOptions): BoxLayout { return options.layout || new BoxLayout(options); } } lumino-2021.12.13/packages/widgets/src/commandpalette.ts000066400000000000000000001220271415564225700230050ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { ArrayExt, StringExt } from '@lumino/algorithm'; import { JSONExt, ReadonlyJSONObject } from '@lumino/coreutils'; import { CommandRegistry } from '@lumino/commands'; import { ElementExt } from '@lumino/domutils'; import { Message } from '@lumino/messaging'; import { ElementDataset, h, VirtualDOM, VirtualElement } from '@lumino/virtualdom'; import { Widget } from './widget'; /** * A widget which displays command items as a searchable palette. */ export class CommandPalette extends Widget { /** * Construct a new command palette. * * @param options - The options for initializing the palette. */ constructor(options: CommandPalette.IOptions) { super({ node: Private.createNode() }); this.addClass('lm-CommandPalette'); /* */ this.addClass('p-CommandPalette'); /* */ this.setFlag(Widget.Flag.DisallowLayout); this.commands = options.commands; this.renderer = options.renderer || CommandPalette.defaultRenderer; this.commands.commandChanged.connect(this._onGenericChange, this); this.commands.keyBindingChanged.connect(this._onGenericChange, this); } /** * Dispose of the resources held by the widget. */ dispose(): void { this._items.length = 0; this._results = null; super.dispose(); } /** * The command registry used by the command palette. */ readonly commands: CommandRegistry; /** * The renderer used by the command palette. */ readonly renderer: CommandPalette.IRenderer; /** * The command palette search node. * * #### Notes * This is the node which contains the search-related elements. */ get searchNode(): HTMLDivElement { return this.node.getElementsByClassName( 'lm-CommandPalette-search' )[0] as HTMLDivElement; } /** * The command palette input node. * * #### Notes * This is the actual input node for the search area. */ get inputNode(): HTMLInputElement { return this.node.getElementsByClassName( 'lm-CommandPalette-input' )[0] as HTMLInputElement; } /** * The command palette content node. * * #### Notes * This is the node which holds the command item nodes. * * Modifying this node directly can lead to undefined behavior. */ get contentNode(): HTMLUListElement { return this.node.getElementsByClassName( 'lm-CommandPalette-content' )[0] as HTMLUListElement; } /** * A read-only array of the command items in the palette. */ get items(): ReadonlyArray { return this._items; } /** * Add a command item to the command palette. * * @param options - The options for creating the command item. * * @returns The command item added to the palette. */ addItem(options: CommandPalette.IItemOptions): CommandPalette.IItem { // Create a new command item for the options. let item = Private.createItem(this.commands, options); // Add the item to the array. this._items.push(item); // Refresh the search results. this.refresh(); // Return the item added to the palette. return item; } /** * Adds command items to the command palette. * * @param items - An array of options for creating each command item. * * @returns The command items added to the palette. */ addItems(items: CommandPalette.IItemOptions[]): CommandPalette.IItem[] { const newItems = items.map(item => Private.createItem(this.commands, item)); newItems.forEach(item => this._items.push(item)); this.refresh(); return newItems; } /** * Remove an item from the command palette. * * @param item - The item to remove from the palette. * * #### Notes * This is a no-op if the item is not in the palette. */ removeItem(item: CommandPalette.IItem): void { this.removeItemAt(this._items.indexOf(item)); } /** * Remove the item at a given index from the command palette. * * @param index - The index of the item to remove. * * #### Notes * This is a no-op if the index is out of range. */ removeItemAt(index: number): void { // Remove the item from the array. let item = ArrayExt.removeAt(this._items, index); // Bail if the index is out of range. if (!item) { return; } // Refresh the search results. this.refresh(); } /** * Remove all items from the command palette. */ clearItems(): void { // Bail if there is nothing to remove. if (this._items.length === 0) { return; } // Clear the array of items. this._items.length = 0; // Refresh the search results. this.refresh(); } /** * Clear the search results and schedule an update. * * #### Notes * This should be called whenever the search results of the palette * should be updated. * * This is typically called automatically by the palette as needed, * but can be called manually if the input text is programatically * changed. * * The rendered results are updated asynchronously. */ refresh(): void { this._results = null; if (this.inputNode.value !== '') { let clear = this.node.getElementsByClassName( 'lm-close-icon' )[0] as HTMLInputElement; clear.style.display = 'inherit'; } else { let clear = this.node.getElementsByClassName( 'lm-close-icon' )[0] as HTMLInputElement; clear.style.display = 'none'; } this.update(); } /** * Handle the DOM events for the command palette. * * @param event - The DOM event sent to the command palette. * * #### Notes * This method implements the DOM `EventListener` interface and is * called in response to events on the command palette's DOM node. * It should not be called directly by user code. */ handleEvent(event: Event): void { switch (event.type) { case 'click': this._evtClick(event as MouseEvent); break; case 'keydown': this._evtKeyDown(event as KeyboardEvent); break; case 'input': this.refresh(); break; case 'focus': case 'blur': this._toggleFocused(); break; } } /** * A message handler invoked on a `'before-attach'` message. */ protected onBeforeAttach(msg: Message): void { this.node.addEventListener('click', this); this.node.addEventListener('keydown', this); this.node.addEventListener('input', this); this.node.addEventListener('focus', this, true); this.node.addEventListener('blur', this, true); } /** * A message handler invoked on an `'after-detach'` message. */ protected onAfterDetach(msg: Message): void { this.node.removeEventListener('click', this); this.node.removeEventListener('keydown', this); this.node.removeEventListener('input', this); this.node.removeEventListener('focus', this, true); this.node.removeEventListener('blur', this, true); } /** * A message handler invoked on an `'activate-request'` message. */ protected onActivateRequest(msg: Message): void { if (this.isAttached) { let input = this.inputNode; input.focus(); input.select(); } } /** * A message handler invoked on an `'update-request'` message. */ protected onUpdateRequest(msg: Message): void { // Fetch the current query text and content node. let query = this.inputNode.value; let contentNode = this.contentNode; // Ensure the search results are generated. let results = this._results; if (!results) { // Generate and store the new search results. results = this._results = Private.search(this._items, query); // Reset the active index. this._activeIndex = query ? ArrayExt.findFirstIndex(results, Private.canActivate) : -1; } // If there is no query and no results, clear the content. if (!query && results.length === 0) { VirtualDOM.render(null, contentNode); return; } // If the is a query but no results, render the empty message. if (query && results.length === 0) { let content = this.renderer.renderEmptyMessage({ query }); VirtualDOM.render(content, contentNode); return; } // Create the render content for the search results. let renderer = this.renderer; let activeIndex = this._activeIndex; let content = new Array(results.length); for (let i = 0, n = results.length; i < n; ++i) { let result = results[i]; if (result.type === 'header') { let indices = result.indices; let category = result.category; content[i] = renderer.renderHeader({ category, indices }); } else { let item = result.item; let indices = result.indices; let active = i === activeIndex; content[i] = renderer.renderItem({ item, indices, active }); } } // Render the search result content. VirtualDOM.render(content, contentNode); // Adjust the scroll position as needed. if (activeIndex < 0 || activeIndex >= results.length) { contentNode.scrollTop = 0; } else { let element = contentNode.children[activeIndex]; ElementExt.scrollIntoViewIfNeeded(contentNode, element); } } /** * Handle the `'click'` event for the command palette. */ private _evtClick(event: MouseEvent): void { // Bail if the click is not the left button. if (event.button !== 0) { return; } // Clear input if the target is clear button if ((event.target as HTMLElement).classList.contains('lm-close-icon')) { this.inputNode.value = ''; this.refresh(); return; } // Find the index of the item which was clicked. let index = ArrayExt.findFirstIndex(this.contentNode.children, node => { return node.contains(event.target as HTMLElement); }); // Bail if the click was not on an item. if (index === -1) { return; } // Kill the event when a content item is clicked. event.preventDefault(); event.stopPropagation(); // Execute the item if possible. this._execute(index); } /** * Handle the `'keydown'` event for the command palette. */ private _evtKeyDown(event: KeyboardEvent): void { if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) { return; } switch (event.keyCode) { case 13: // Enter event.preventDefault(); event.stopPropagation(); this._execute(this._activeIndex); break; case 38: // Up Arrow event.preventDefault(); event.stopPropagation(); this._activatePreviousItem(); break; case 40: // Down Arrow event.preventDefault(); event.stopPropagation(); this._activateNextItem(); break; } } /** * Activate the next enabled command item. */ private _activateNextItem(): void { // Bail if there are no search results. if (!this._results || this._results.length === 0) { return; } // Find the next enabled item index. let ai = this._activeIndex; let n = this._results.length; let start = ai < n - 1 ? ai + 1 : 0; let stop = start === 0 ? n - 1 : start - 1; this._activeIndex = ArrayExt.findFirstIndex( this._results, Private.canActivate, start, stop ); // Schedule an update of the items. this.update(); } /** * Activate the previous enabled command item. */ private _activatePreviousItem(): void { // Bail if there are no search results. if (!this._results || this._results.length === 0) { return; } // Find the previous enabled item index. let ai = this._activeIndex; let n = this._results.length; let start = ai <= 0 ? n - 1 : ai - 1; let stop = start === n - 1 ? 0 : start + 1; this._activeIndex = ArrayExt.findLastIndex( this._results, Private.canActivate, start, stop ); // Schedule an update of the items. this.update(); } /** * Execute the command item at the given index, if possible. */ private _execute(index: number): void { // Bail if there are no search results. if (!this._results) { return; } // Bail if the index is out of range. let part = this._results[index]; if (!part) { return; } // Update the search text if the item is a header. if (part.type === 'header') { let input = this.inputNode; input.value = `${part.category.toLowerCase()} `; input.focus(); this.refresh(); return; } // Bail if item is not enabled. if (!part.item.isEnabled) { return; } // Execute the item. this.commands.execute(part.item.command, part.item.args); // Clear the query text. this.inputNode.value = ''; // Refresh the search results. this.refresh(); } /** * Toggle the focused modifier based on the input node focus state. */ private _toggleFocused(): void { let focused = document.activeElement === this.inputNode; this.toggleClass('lm-mod-focused', focused); /* */ this.toggleClass('p-mod-focused', focused); /* */ } /** * A signal handler for generic command changes. */ private _onGenericChange(): void { this.refresh(); } private _activeIndex = -1; private _items: CommandPalette.IItem[] = []; private _results: Private.SearchResult[] | null = null; } /** * The namespace for the `CommandPalette` class statics. */ export namespace CommandPalette { /** * An options object for creating a command palette. */ export interface IOptions { /** * The command registry for use with the command palette. */ commands: CommandRegistry; /** * A custom renderer for use with the command palette. * * The default is a shared renderer instance. */ renderer?: IRenderer; } /** * An options object for creating a command item. */ export interface IItemOptions { /** * The category for the item. */ category: string; /** * The command to execute when the item is triggered. */ command: string; /** * The arguments for the command. * * The default value is an empty object. */ args?: ReadonlyJSONObject; /** * The rank for the command item. * * The rank is used as a tie-breaker when ordering command items * for display. Items are sorted in the following order: * 1. Text match (lower is better) * 2. Category (locale order) * 3. Rank (lower is better) * 4. Label (locale order) * * The default rank is `Infinity`. */ rank?: number; } /** * An object which represents an item in a command palette. * * #### Notes * Item objects are created automatically by a command palette. */ export interface IItem { /** * The command to execute when the item is triggered. */ readonly command: string; /** * The arguments for the command. */ readonly args: ReadonlyJSONObject; /** * The category for the command item. */ readonly category: string; /** * The rank for the command item. */ readonly rank: number; /** * The display label for the command item. */ readonly label: string; /** * The display caption for the command item. */ readonly caption: string; /** * The icon renderer for the command item. */ readonly icon: | VirtualElement.IRenderer | undefined /* */ | string /* */; /** * The icon class for the command item. */ readonly iconClass: string; /** * The icon label for the command item. */ readonly iconLabel: string; /** * The extra class name for the command item. */ readonly className: string; /** * The dataset for the command item. */ readonly dataset: CommandRegistry.Dataset; /** * Whether the command item is enabled. */ readonly isEnabled: boolean; /** * Whether the command item is toggled. */ readonly isToggled: boolean; /** * Whether the command item is toggleable. */ readonly isToggleable: boolean; /** * Whether the command item is visible. */ readonly isVisible: boolean; /** * The key binding for the command item. */ readonly keyBinding: CommandRegistry.IKeyBinding | null; } /** * The render data for a command palette header. */ export interface IHeaderRenderData { /** * The category of the header. */ readonly category: string; /** * The indices of the matched characters in the category. */ readonly indices: ReadonlyArray | null; } /** * The render data for a command palette item. */ export interface IItemRenderData { /** * The command palette item to render. */ readonly item: IItem; /** * The indices of the matched characters in the label. */ readonly indices: ReadonlyArray | null; /** * Whether the item is the active item. */ readonly active: boolean; } /** * The render data for a command palette empty message. */ export interface IEmptyMessageRenderData { /** * The query which failed to match any commands. */ query: string; } /** * A renderer for use with a command palette. */ export interface IRenderer { /** * Render the virtual element for a command palette header. * * @param data - The data to use for rendering the header. * * @returns A virtual element representing the header. */ renderHeader(data: IHeaderRenderData): VirtualElement; /** * Render the virtual element for a command palette item. * * @param data - The data to use for rendering the item. * * @returns A virtual element representing the item. * * #### Notes * The command palette will not render invisible items. */ renderItem(data: IItemRenderData): VirtualElement; /** * Render the empty results message for a command palette. * * @param data - The data to use for rendering the message. * * @returns A virtual element representing the message. */ renderEmptyMessage(data: IEmptyMessageRenderData): VirtualElement; } /** * The default implementation of `IRenderer`. */ export class Renderer implements IRenderer { /** * Render the virtual element for a command palette header. * * @param data - The data to use for rendering the header. * * @returns A virtual element representing the header. */ renderHeader(data: IHeaderRenderData): VirtualElement { let content = this.formatHeader(data); return h.li( { className: 'lm-CommandPalette-header' + /* */ ' p-CommandPalette-header' /* */ }, content ); } /** * Render the virtual element for a command palette item. * * @param data - The data to use for rendering the item. * * @returns A virtual element representing the item. */ renderItem(data: IItemRenderData): VirtualElement { let className = this.createItemClass(data); let dataset = this.createItemDataset(data); if (data.item.isToggleable) { return h.li( { className, dataset, role: 'checkbox', 'aria-checked': `${data.item.isToggled}` }, this.renderItemIcon(data), this.renderItemContent(data), this.renderItemShortcut(data) ); } return h.li( { className, dataset }, this.renderItemIcon(data), this.renderItemContent(data), this.renderItemShortcut(data) ); } /** * Render the empty results message for a command palette. * * @param data - The data to use for rendering the message. * * @returns A virtual element representing the message. */ renderEmptyMessage(data: IEmptyMessageRenderData): VirtualElement { let content = this.formatEmptyMessage(data); return h.li( { className: 'lm-CommandPalette-emptyMessage' + /* */ ' p-CommandPalette-emptyMessage' /* */ }, content ); } /** * Render the icon for a command palette item. * * @param data - The data to use for rendering the icon. * * @returns A virtual element representing the icon. */ renderItemIcon(data: IItemRenderData): VirtualElement { let className = this.createIconClass(data); /* */ if (typeof data.item.icon === 'string') { return h.div({ className }, data.item.iconLabel); } /* */ // if data.item.icon is undefined, it will be ignored return h.div({ className }, data.item.icon!, data.item.iconLabel); } /** * Render the content for a command palette item. * * @param data - The data to use for rendering the content. * * @returns A virtual element representing the content. */ renderItemContent(data: IItemRenderData): VirtualElement { return h.div( { className: 'lm-CommandPalette-itemContent' + /* */ ' p-CommandPalette-itemContent' /* */ }, this.renderItemLabel(data), this.renderItemCaption(data) ); } /** * Render the label for a command palette item. * * @param data - The data to use for rendering the label. * * @returns A virtual element representing the label. */ renderItemLabel(data: IItemRenderData): VirtualElement { let content = this.formatItemLabel(data); return h.div( { className: 'lm-CommandPalette-itemLabel' + /* */ ' p-CommandPalette-itemLabel' /* */ }, content ); } /** * Render the caption for a command palette item. * * @param data - The data to use for rendering the caption. * * @returns A virtual element representing the caption. */ renderItemCaption(data: IItemRenderData): VirtualElement { let content = this.formatItemCaption(data); return h.div( { className: 'lm-CommandPalette-itemCaption' + /* */ ' p-CommandPalette-itemCaption' /* */ }, content ); } /** * Render the shortcut for a command palette item. * * @param data - The data to use for rendering the shortcut. * * @returns A virtual element representing the shortcut. */ renderItemShortcut(data: IItemRenderData): VirtualElement { let content = this.formatItemShortcut(data); return h.div( { className: 'lm-CommandPalette-itemShortcut' + /* */ ' p-CommandPalette-itemShortcut' /* */ }, content ); } /** * Create the class name for the command palette item. * * @param data - The data to use for the class name. * * @returns The full class name for the command palette item. */ createItemClass(data: IItemRenderData): string { // Set up the initial class name. let name = 'lm-CommandPalette-item'; /* */ name += ' p-CommandPalette-item'; /* */ // Add the boolean state classes. if (!data.item.isEnabled) { name += ' lm-mod-disabled'; /* */ name += ' p-mod-disabled'; /* */ } if (data.item.isToggled) { name += ' lm-mod-toggled'; /* */ name += ' p-mod-toggled'; /* */ } if (data.active) { name += ' lm-mod-active'; /* */ name += ' p-mod-active'; /* */ } // Add the extra class. let extra = data.item.className; if (extra) { name += ` ${extra}`; } // Return the complete class name. return name; } /** * Create the dataset for the command palette item. * * @param data - The data to use for creating the dataset. * * @returns The dataset for the command palette item. */ createItemDataset(data: IItemRenderData): ElementDataset { return { ...data.item.dataset, command: data.item.command }; } /** * Create the class name for the command item icon. * * @param data - The data to use for the class name. * * @returns The full class name for the item icon. */ createIconClass(data: IItemRenderData): string { let name = 'lm-CommandPalette-itemIcon'; /* */ name += ' p-CommandPalette-itemIcon'; /* */ let extra = data.item.iconClass; return extra ? `${name} ${extra}` : name; } /** * Create the render content for the header node. * * @param data - The data to use for the header content. * * @returns The content to add to the header node. */ formatHeader(data: IHeaderRenderData): h.Child { if (!data.indices || data.indices.length === 0) { return data.category; } return StringExt.highlight(data.category, data.indices, h.mark); } /** * Create the render content for the empty message node. * * @param data - The data to use for the empty message content. * * @returns The content to add to the empty message node. */ formatEmptyMessage(data: IEmptyMessageRenderData): h.Child { return `No commands found that match '${data.query}'`; } /** * Create the render content for the item shortcut node. * * @param data - The data to use for the shortcut content. * * @returns The content to add to the shortcut node. */ formatItemShortcut(data: IItemRenderData): h.Child { let kb = data.item.keyBinding; return kb ? kb.keys.map(CommandRegistry.formatKeystroke).join(', ') : null; } /** * Create the render content for the item label node. * * @param data - The data to use for the label content. * * @returns The content to add to the label node. */ formatItemLabel(data: IItemRenderData): h.Child { if (!data.indices || data.indices.length === 0) { return data.item.label; } return StringExt.highlight(data.item.label, data.indices, h.mark); } /** * Create the render content for the item caption node. * * @param data - The data to use for the caption content. * * @returns The content to add to the caption node. */ formatItemCaption(data: IItemRenderData): h.Child { return data.item.caption; } } /** * The default `Renderer` instance. */ export const defaultRenderer = new Renderer(); } /** * The namespace for the module implementation details. */ namespace Private { /** * Create the DOM node for a command palette. */ export function createNode(): HTMLDivElement { let node = document.createElement('div'); let search = document.createElement('div'); let wrapper = document.createElement('div'); let input = document.createElement('input'); let content = document.createElement('ul'); let clear = document.createElement('button'); search.className = 'lm-CommandPalette-search'; wrapper.className = 'lm-CommandPalette-wrapper'; input.className = 'lm-CommandPalette-input'; clear.className = 'lm-close-icon'; content.className = 'lm-CommandPalette-content'; /* */ search.classList.add('p-CommandPalette-search'); wrapper.classList.add('p-CommandPalette-wrapper'); input.classList.add('p-CommandPalette-input'); content.classList.add('p-CommandPalette-content'); /* */ input.spellcheck = false; wrapper.appendChild(input); wrapper.appendChild(clear); search.appendChild(wrapper); node.appendChild(search); node.appendChild(content); return node; } /** * Create a new command item from a command registry and options. */ export function createItem( commands: CommandRegistry, options: CommandPalette.IItemOptions ): CommandPalette.IItem { return new CommandItem(commands, options); } /** * A search result object for a header label. */ export interface IHeaderResult { /** * The discriminated type of the object. */ readonly type: 'header'; /** * The category for the header. */ readonly category: string; /** * The indices of the matched category characters. */ readonly indices: ReadonlyArray | null; } /** * A search result object for a command item. */ export interface IItemResult { /** * The discriminated type of the object. */ readonly type: 'item'; /** * The command item which was matched. */ readonly item: CommandPalette.IItem; /** * The indices of the matched label characters. */ readonly indices: ReadonlyArray | null; } /** * A type alias for a search result item. */ export type SearchResult = IHeaderResult | IItemResult; /** * Search an array of command items for fuzzy matches. */ export function search( items: CommandPalette.IItem[], query: string ): SearchResult[] { // Fuzzy match the items for the query. let scores = matchItems(items, query); // Sort the items based on their score. scores.sort(scoreCmp); // Create the results for the search. return createResults(scores); } /** * Test whether a result item can be activated. */ export function canActivate(result: SearchResult): boolean { return result.type === 'item' && result.item.isEnabled; } /** * Normalize a category for a command item. */ function normalizeCategory(category: string): string { return category.trim().replace(/\s+/g, ' '); } /** * Normalize the query text for a fuzzy search. */ function normalizeQuery(text: string): string { return text.replace(/\s+/g, '').toLowerCase(); } /** * An enum of the supported match types. */ const enum MatchType { Label, Category, Split, Default } /** * A text match score with associated command item. */ interface IScore { /** * The numerical type for the text match. */ matchType: MatchType; /** * The numerical score for the text match. */ score: number; /** * The indices of the matched category characters. */ categoryIndices: number[] | null; /** * The indices of the matched label characters. */ labelIndices: number[] | null; /** * The command item associated with the match. */ item: CommandPalette.IItem; } /** * Perform a fuzzy match on an array of command items. */ function matchItems(items: CommandPalette.IItem[], query: string): IScore[] { // Normalize the query text to lower case with no whitespace. query = normalizeQuery(query); // Create the array to hold the scores. let scores: IScore[] = []; // Iterate over the items and match against the query. for (let i = 0, n = items.length; i < n; ++i) { // Ignore items which are not visible. let item = items[i]; if (!item.isVisible) { continue; } // If the query is empty, all items are matched by default. if (!query) { scores.push({ matchType: MatchType.Default, categoryIndices: null, labelIndices: null, score: 0, item }); continue; } // Run the fuzzy search for the item and query. let score = fuzzySearch(item, query); // Ignore the item if it is not a match. if (!score) { continue; } // Penalize disabled items. // TODO - push disabled items all the way down in sort cmp? if (!item.isEnabled) { score.score += 1000; } // Add the score to the results. scores.push(score); } // Return the final array of scores. return scores; } /** * Perform a fuzzy search on a single command item. */ function fuzzySearch( item: CommandPalette.IItem, query: string ): IScore | null { // Create the source text to be searched. let category = item.category.toLowerCase(); let label = item.label.toLowerCase(); let source = `${category} ${label}`; // Set up the match score and indices array. let score = Infinity; let indices: number[] | null = null; // The regex for search word boundaries let rgx = /\b\w/g; // Search the source by word boundary. // eslint-disable-next-line no-constant-condition while (true) { // Find the next word boundary in the source. let rgxMatch = rgx.exec(source); // Break if there is no more source context. if (!rgxMatch) { break; } // Run the string match on the relevant substring. let match = StringExt.matchSumOfDeltas(source, query, rgxMatch.index); // Break if there is no match. if (!match) { break; } // Update the match if the score is better. if (match && match.score <= score) { score = match.score; indices = match.indices; } } // Bail if there was no match. if (!indices || score === Infinity) { return null; } // Compute the pivot index between category and label text. let pivot = category.length + 1; // Find the slice index to separate matched indices. let j = ArrayExt.lowerBound(indices, pivot, (a, b) => a - b); // Extract the matched category and label indices. let categoryIndices = indices.slice(0, j); let labelIndices = indices.slice(j); // Adjust the label indices for the pivot offset. for (let i = 0, n = labelIndices.length; i < n; ++i) { labelIndices[i] -= pivot; } // Handle a pure label match. if (categoryIndices.length === 0) { return { matchType: MatchType.Label, categoryIndices: null, labelIndices, score, item }; } // Handle a pure category match. if (labelIndices.length === 0) { return { matchType: MatchType.Category, categoryIndices, labelIndices: null, score, item }; } // Handle a split match. return { matchType: MatchType.Split, categoryIndices, labelIndices, score, item }; } /** * A sort comparison function for a match score. */ function scoreCmp(a: IScore, b: IScore): number { // First compare based on the match type let m1 = a.matchType - b.matchType; if (m1 !== 0) { return m1; } // Otherwise, compare based on the match score. let d1 = a.score - b.score; if (d1 !== 0) { return d1; } // Find the match index based on the match type. let i1 = 0; let i2 = 0; switch (a.matchType) { case MatchType.Label: i1 = a.labelIndices![0]; i2 = b.labelIndices![0]; break; case MatchType.Category: case MatchType.Split: i1 = a.categoryIndices![0]; i2 = b.categoryIndices![0]; break; } // Compare based on the match index. if (i1 !== i2) { return i1 - i2; } // Otherwise, compare by category. let d2 = a.item.category.localeCompare(b.item.category); if (d2 !== 0) { return d2; } // Otherwise, compare by rank. let r1 = a.item.rank; let r2 = b.item.rank; if (r1 !== r2) { return r1 < r2 ? -1 : 1; // Infinity safe } // Finally, compare by label. return a.item.label.localeCompare(b.item.label); } /** * Create the results from an array of sorted scores. */ function createResults(scores: IScore[]): SearchResult[] { // Set up an array to track which scores have been visited. let visited = new Array(scores.length); ArrayExt.fill(visited, false); // Set up the search results array. let results: SearchResult[] = []; // Iterate over each score in the array. for (let i = 0, n = scores.length; i < n; ++i) { // Ignore a score which has already been processed. if (visited[i]) { continue; } // Extract the current item and indices. let { item, categoryIndices } = scores[i]; // Extract the category for the current item. let category = item.category; // Add the header result for the category. results.push({ type: 'header', category, indices: categoryIndices }); // Find the rest of the scores with the same category. for (let j = i; j < n; ++j) { // Ignore a score which has already been processed. if (visited[j]) { continue; } // Extract the data for the current score. let { item, labelIndices } = scores[j]; // Ignore an item with a different category. if (item.category !== category) { continue; } // Create the item result for the score. results.push({ type: 'item', item, indices: labelIndices }); // Mark the score as processed. visited[j] = true; } } // Return the final results. return results; } /** * A concrete implementation of `CommandPalette.IItem`. */ class CommandItem implements CommandPalette.IItem { /** * Construct a new command item. */ constructor( commands: CommandRegistry, options: CommandPalette.IItemOptions ) { this._commands = commands; this.category = normalizeCategory(options.category); this.command = options.command; this.args = options.args || JSONExt.emptyObject; this.rank = options.rank !== undefined ? options.rank : Infinity; } /** * The category for the command item. */ readonly category: string; /** * The command to execute when the item is triggered. */ readonly command: string; /** * The arguments for the command. */ readonly args: ReadonlyJSONObject; /** * The rank for the command item. */ readonly rank: number; /** * The display label for the command item. */ get label(): string { return this._commands.label(this.command, this.args); } /** * The icon renderer for the command item. */ get icon(): | VirtualElement.IRenderer | undefined /* */ | string /* */ { return this._commands.icon(this.command, this.args); } /** * The icon class for the command item. */ get iconClass(): string { return this._commands.iconClass(this.command, this.args); } /** * The icon label for the command item. */ get iconLabel(): string { return this._commands.iconLabel(this.command, this.args); } /** * The display caption for the command item. */ get caption(): string { return this._commands.caption(this.command, this.args); } /** * The extra class name for the command item. */ get className(): string { return this._commands.className(this.command, this.args); } /** * The dataset for the command item. */ get dataset(): CommandRegistry.Dataset { return this._commands.dataset(this.command, this.args); } /** * Whether the command item is enabled. */ get isEnabled(): boolean { return this._commands.isEnabled(this.command, this.args); } /** * Whether the command item is toggled. */ get isToggled(): boolean { return this._commands.isToggled(this.command, this.args); } /** * Whether the command item is toggleable. */ get isToggleable(): boolean { return this._commands.isToggleable(this.command, this.args); } /** * Whether the command item is visible. */ get isVisible(): boolean { return this._commands.isVisible(this.command, this.args); } /** * The key binding for the command item. */ get keyBinding(): CommandRegistry.IKeyBinding | null { let { command, args } = this; return ( ArrayExt.findLastValue(this._commands.keyBindings, kb => { return kb.command === command && JSONExt.deepEqual(kb.args, args); }) || null ); } private _commands: CommandRegistry; } } lumino-2021.12.13/packages/widgets/src/contextmenu.ts000066400000000000000000000234241415564225700223620ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { ArrayExt, each } from '@lumino/algorithm'; import { CommandRegistry } from '@lumino/commands'; import { DisposableDelegate, IDisposable } from '@lumino/disposable'; import { Selector } from '@lumino/domutils'; import { Menu } from './menu'; /** * An object which implements a universal context menu. * * #### Notes * The items shown in the context menu are determined by CSS selector * matching against the DOM hierarchy at the site of the mouse click. * This is similar in concept to how keyboard shortcuts are matched * in the command registry. */ export class ContextMenu { /** * Construct a new context menu. * * @param options - The options for initializing the menu. */ constructor(options: ContextMenu.IOptions) { const { groupByTarget, sortBySelector, ...others } = options; this.menu = new Menu(others); this._groupByTarget = groupByTarget !== false; this._sortBySelector = sortBySelector !== false; } /** * The menu widget which displays the matched context items. */ readonly menu: Menu; /** * Add an item to the context menu. * * @param options - The options for creating the item. * * @returns A disposable which will remove the item from the menu. */ addItem(options: ContextMenu.IItemOptions): IDisposable { // Create an item from the given options. let item = Private.createItem(options, this._idTick++); // Add the item to the internal array. this._items.push(item); // Return a disposable which will remove the item. return new DisposableDelegate(() => { ArrayExt.removeFirstOf(this._items, item); }); } /** * Open the context menu in response to a `'contextmenu'` event. * * @param event - The `'contextmenu'` event of interest. * * @returns `true` if the menu was opened, or `false` if no items * matched the event and the menu was not opened. * * #### Notes * This method will populate the context menu with items which match * the propagation path of the event, then open the menu at the mouse * position indicated by the event. */ open(event: MouseEvent): boolean { // Clear the current contents of the context menu. this.menu.clearItems(); // Bail early if there are no items to match. if (this._items.length === 0) { return false; } // Find the matching items for the event. let items = Private.matchItems( this._items, event, this._groupByTarget, this._sortBySelector ); // Bail if there are no matching items. if (!items || items.length === 0) { return false; } // Add the filtered items to the menu. each(items, item => { this.menu.addItem(item); }); // Open the context menu at the current mouse position. this.menu.open(event.clientX, event.clientY); // Indicate success. return true; } private _groupByTarget: boolean = true; private _idTick = 0; private _items: Private.IItem[] = []; private _sortBySelector: boolean = true; } /** * The namespace for the `ContextMenu` class statics. */ export namespace ContextMenu { /** * An options object for initializing a context menu. */ export interface IOptions { /** * The command registry to use with the context menu. */ commands: CommandRegistry; /** * A custom renderer for use with the context menu. */ renderer?: Menu.IRenderer; /** * Whether to sort by selector and rank or only rank. * * Default true. */ sortBySelector?: boolean; /** * Whether to group items following the DOM hierarchy. * * Default true. * * #### Note * If true, when the mouse event occurs on element `span` within `div.top`, * the items matching `div.top` will be shown before the ones matching `body`. */ groupByTarget?: boolean; } /** * An options object for creating a context menu item. */ export interface IItemOptions extends Menu.IItemOptions { /** * The CSS selector for the context menu item. * * The context menu item will only be displayed in the context menu * when the selector matches a node on the propagation path of the * contextmenu event. This allows the menu item to be restricted to * user-defined contexts. * * The selector must not contain commas. */ selector: string; /** * The rank for the item. * * The rank is used as a tie-breaker when ordering context menu * items for display. Items are sorted in the following order: * 1. Depth in the DOM tree (deeper is better) * 2. Selector specificity (higher is better) * 3. Rank (lower is better) * 4. Insertion order * * The default rank is `Infinity`. */ rank?: number; } } /** * The namespace for the module implementation details. */ namespace Private { /** * A normalized item for a context menu. */ export interface IItem extends Menu.IItemOptions { /** * The selector for the item. */ selector: string; /** * The rank for the item. */ rank: number; /** * The tie-breaking id for the item. */ id: number; } /** * Create a normalized context menu item from an options object. */ export function createItem( options: ContextMenu.IItemOptions, id: number ): IItem { let selector = validateSelector(options.selector); let rank = options.rank !== undefined ? options.rank : Infinity; return { ...options, selector, rank, id }; } /** * Find the items which match a context menu event. * * The results are sorted by DOM level, specificity, and rank. */ export function matchItems( items: IItem[], event: MouseEvent, groupByTarget: boolean, sortBySelector: boolean ): IItem[] | null { // Look up the target of the event. let target = event.target as Element | null; // Bail if there is no target. if (!target) { return null; } // Look up the current target of the event. let currentTarget = event.currentTarget as Element | null; // Bail if there is no current target. if (!currentTarget) { return null; } // There are some third party libraries that cause the `target` to // be detached from the DOM before lumino can process the event. // If that happens, search for a new target node by point. If that // node is still dangling, bail. if (!currentTarget.contains(target)) { target = document.elementFromPoint(event.clientX, event.clientY); if (!target || !currentTarget.contains(target)) { return null; } } // Set up the result array. let result: IItem[] = []; // Copy the items array to allow in-place modification. let availableItems: Array = items.slice(); // Walk up the DOM hierarchy searching for matches. while (target !== null) { // Set up the match array for this DOM level. let matches: IItem[] = []; // Search the remaining items for matches. for (let i = 0, n = availableItems.length; i < n; ++i) { // Fetch the item. let item = availableItems[i]; // Skip items which are already consumed. if (!item) { continue; } // Skip items which do not match the element. if (!Selector.matches(target, item.selector)) { continue; } // Add the matched item to the result for this DOM level. matches.push(item); // Mark the item as consumed. availableItems[i] = null; } // Sort the matches for this level and add them to the results. if (matches.length !== 0) { if (groupByTarget) { matches.sort(sortBySelector ? itemCmp : itemCmpRank); } result.push(...matches); } // Stop searching at the limits of the DOM range. if (target === currentTarget) { break; } // Step to the parent DOM level. target = target.parentElement; } if (!groupByTarget) { result.sort(sortBySelector ? itemCmp : itemCmpRank); } // Return the matched and sorted results. return result; } /** * Validate the selector for a menu item. * * This returns the validated selector, or throws if the selector is * invalid or contains commas. */ function validateSelector(selector: string): string { if (selector.indexOf(',') !== -1) { throw new Error(`Selector cannot contain commas: ${selector}`); } if (!Selector.isValid(selector)) { throw new Error(`Invalid selector: ${selector}`); } return selector; } /** * A sort comparison function for a context menu item by ranks. */ function itemCmpRank(a: IItem, b: IItem): number { // Sort based on rank. let r1 = a.rank; let r2 = b.rank; if (r1 !== r2) { return r1 < r2 ? -1 : 1; // Infinity-safe } // When all else fails, sort by item id. return a.id - b.id; } /** * A sort comparison function for a context menu item by selectors and ranks. */ function itemCmp(a: IItem, b: IItem): number { // Sort first based on selector specificity. let s1 = Selector.calculateSpecificity(a.selector); let s2 = Selector.calculateSpecificity(b.selector); if (s1 !== s2) { return s2 - s1; } // If specificities are equal return itemCmpRank(a, b); } } lumino-2021.12.13/packages/widgets/src/docklayout.ts000066400000000000000000001700721415564225700221710ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { ArrayExt, chain, ChainIterator, each, empty, IIterator, map, once, reduce } from '@lumino/algorithm'; import { ElementExt } from '@lumino/domutils'; import { Message, MessageLoop } from '@lumino/messaging'; import { BoxEngine, BoxSizer } from './boxengine'; import { Layout, LayoutItem } from './layout'; import { TabBar } from './tabbar'; import Utils from './utils'; import { Widget } from './widget'; /** * A layout which provides a flexible docking arrangement. * * #### Notes * The consumer of this layout is responsible for handling all signals * from the generated tab bars and managing the visibility of widgets * and tab bars as needed. */ export class DockLayout extends Layout { /** * Construct a new dock layout. * * @param options - The options for initializing the layout. */ constructor(options: DockLayout.IOptions) { super(); this.renderer = options.renderer; if (options.spacing !== undefined) { this._spacing = Utils.clampDimension(options.spacing); } this._hiddenMode = options.hiddenMode !== undefined ? options.hiddenMode : Widget.HiddenMode.Display; } /** * Dispose of the resources held by the layout. * * #### Notes * This will clear and dispose all widgets in the layout. */ dispose(): void { // Get an iterator over the widgets in the layout. let widgets = this.iter(); // Dispose of the layout items. this._items.forEach(item => { item.dispose(); }); // Clear the layout state before disposing the widgets. this._box = null; this._root = null; this._items.clear(); // Dispose of the widgets contained in the old layout root. each(widgets, widget => { widget.dispose(); }); // Dispose of the base class. super.dispose(); } /** * The renderer used by the dock layout. */ readonly renderer: DockLayout.IRenderer; /** * The method for hiding child widgets. * * #### Notes * If there is only one child widget, `Display` hiding mode will be used * regardless of this setting. */ get hiddenMode(): Widget.HiddenMode { return this._hiddenMode; } set hiddenMode(v: Widget.HiddenMode) { if (this._hiddenMode === v) { return; } this._hiddenMode = v; each(this.tabBars(), bar => { if (bar.titles.length > 1) { bar.titles.forEach(title => { title.owner.hiddenMode = this._hiddenMode; }); } }); } /** * Get the inter-element spacing for the dock layout. */ get spacing(): number { return this._spacing; } /** * Set the inter-element spacing for the dock layout. */ set spacing(value: number) { value = Utils.clampDimension(value); if (this._spacing === value) { return; } this._spacing = value; if (!this.parent) { return; } this.parent.fit(); } /** * Whether the dock layout is empty. */ get isEmpty(): boolean { return this._root === null; } /** * Create an iterator over all widgets in the layout. * * @returns A new iterator over the widgets in the layout. * * #### Notes * This iterator includes the generated tab bars. */ iter(): IIterator { return this._root ? this._root.iterAllWidgets() : empty(); } /** * Create an iterator over the user widgets in the layout. * * @returns A new iterator over the user widgets in the layout. * * #### Notes * This iterator does not include the generated tab bars. */ widgets(): IIterator { return this._root ? this._root.iterUserWidgets() : empty(); } /** * Create an iterator over the selected widgets in the layout. * * @returns A new iterator over the selected user widgets. * * #### Notes * This iterator yields the widgets corresponding to the current tab * of each tab bar in the layout. */ selectedWidgets(): IIterator { return this._root ? this._root.iterSelectedWidgets() : empty(); } /** * Create an iterator over the tab bars in the layout. * * @returns A new iterator over the tab bars in the layout. * * #### Notes * This iterator does not include the user widgets. */ tabBars(): IIterator> { return this._root ? this._root.iterTabBars() : empty>(); } /** * Create an iterator over the handles in the layout. * * @returns A new iterator over the handles in the layout. */ handles(): IIterator { return this._root ? this._root.iterHandles() : empty(); } /** * Move a handle to the given offset position. * * @param handle - The handle to move. * * @param offsetX - The desired offset X position of the handle. * * @param offsetY - The desired offset Y position of the handle. * * #### Notes * If the given handle is not contained in the layout, this is no-op. * * The handle will be moved as close as possible to the desired * position without violating any of the layout constraints. * * Only one of the coordinates is used depending on the orientation * of the handle. This method accepts both coordinates to make it * easy to invoke from a mouse move event without needing to know * the handle orientation. */ moveHandle(handle: HTMLDivElement, offsetX: number, offsetY: number): void { // Bail early if there is no root or if the handle is hidden. let hidden = handle.classList.contains('lm-mod-hidden'); /* */ hidden = hidden || handle.classList.contains('p-mod-hidden'); /* */ if (!this._root || hidden) { return; } // Lookup the split node for the handle. let data = this._root.findSplitNode(handle); if (!data) { return; } // Compute the desired delta movement for the handle. let delta: number; if (data.node.orientation === 'horizontal') { delta = offsetX - handle.offsetLeft; } else { delta = offsetY - handle.offsetTop; } // Bail if there is no handle movement. if (delta === 0) { return; } // Prevent sibling resizing unless needed. data.node.holdSizes(); // Adjust the sizers to reflect the handle movement. BoxEngine.adjust(data.node.sizers, data.index, delta); // Update the layout of the widgets. if (this.parent) { this.parent.update(); } } /** * Save the current configuration of the dock layout. * * @returns A new config object for the current layout state. * * #### Notes * The return value can be provided to the `restoreLayout` method * in order to restore the layout to its current configuration. */ saveLayout(): DockLayout.ILayoutConfig { // Bail early if there is no root. if (!this._root) { return { main: null }; } // Hold the current sizes in the layout tree. this._root.holdAllSizes(); // Return the layout config. return { main: this._root.createConfig() }; } /** * Restore the layout to a previously saved configuration. * * @param config - The layout configuration to restore. * * #### Notes * Widgets which currently belong to the layout but which are not * contained in the config will be unparented. */ restoreLayout(config: DockLayout.ILayoutConfig): void { // Create the widget set for validating the config. let widgetSet = new Set(); // Normalize the main area config and collect the widgets. let mainConfig: DockLayout.AreaConfig | null; if (config.main) { mainConfig = Private.normalizeAreaConfig(config.main, widgetSet); } else { mainConfig = null; } // Create iterators over the old content. let oldWidgets = this.widgets(); let oldTabBars = this.tabBars(); let oldHandles = this.handles(); // Clear the root before removing the old content. this._root = null; // Unparent the old widgets which are not in the new config. each(oldWidgets, widget => { if (!widgetSet.has(widget)) { widget.parent = null; } }); // Dispose of the old tab bars. each(oldTabBars, tabBar => { tabBar.dispose(); }); // Remove the old handles. each(oldHandles, handle => { if (handle.parentNode) { handle.parentNode.removeChild(handle); } }); // Reparent the new widgets to the current parent. widgetSet.forEach(widget => { widget.parent = this.parent; }); // Create the root node for the new config. if (mainConfig) { this._root = Private.realizeAreaConfig(mainConfig, { createTabBar: () => this._createTabBar(), createHandle: () => this._createHandle() }); } else { this._root = null; } // If there is no parent, there is nothing more to do. if (!this.parent) { return; } // Attach the new widgets to the parent. widgetSet.forEach(widget => { this.attachWidget(widget); }); // Post a fit request to the parent. this.parent.fit(); } /** * Add a widget to the dock layout. * * @param widget - The widget to add to the dock layout. * * @param options - The additional options for adding the widget. * * #### Notes * The widget will be moved if it is already contained in the layout. * * An error will be thrown if the reference widget is invalid. */ addWidget(widget: Widget, options: DockLayout.IAddOptions = {}): void { // Parse the options. let ref = options.ref || null; let mode = options.mode || 'tab-after'; // Find the tab node which holds the reference widget. let refNode: Private.TabLayoutNode | null = null; if (this._root && ref) { refNode = this._root.findTabNode(ref); } // Throw an error if the reference widget is invalid. if (ref && !refNode) { throw new Error('Reference widget is not in the layout.'); } // Reparent the widget to the current layout parent. widget.parent = this.parent; // Insert the widget according to the insert mode. switch (mode) { case 'tab-after': this._insertTab(widget, ref, refNode, true); break; case 'tab-before': this._insertTab(widget, ref, refNode, false); break; case 'split-top': this._insertSplit(widget, ref, refNode, 'vertical', false); break; case 'split-left': this._insertSplit(widget, ref, refNode, 'horizontal', false); break; case 'split-right': this._insertSplit(widget, ref, refNode, 'horizontal', true); break; case 'split-bottom': this._insertSplit(widget, ref, refNode, 'vertical', true); break; } // Do nothing else if there is no parent widget. if (!this.parent) { return; } // Ensure the widget is attached to the parent widget. this.attachWidget(widget); // Post a fit request for the parent widget. this.parent.fit(); } /** * Remove a widget from the layout. * * @param widget - The widget to remove from the layout. * * #### Notes * A widget is automatically removed from the layout when its `parent` * is set to `null`. This method should only be invoked directly when * removing a widget from a layout which has yet to be installed on a * parent widget. * * This method does *not* modify the widget's `parent`. */ removeWidget(widget: Widget): void { // Remove the widget from its current layout location. this._removeWidget(widget); // Do nothing else if there is no parent widget. if (!this.parent) { return; } // Detach the widget from the parent widget. this.detachWidget(widget); // Post a fit request for the parent widget. this.parent.fit(); } /** * Find the tab area which contains the given client position. * * @param clientX - The client X position of interest. * * @param clientY - The client Y position of interest. * * @returns The geometry of the tab area at the given position, or * `null` if there is no tab area at the given position. */ hitTestTabAreas( clientX: number, clientY: number ): DockLayout.ITabAreaGeometry | null { // Bail early if hit testing cannot produce valid results. if (!this._root || !this.parent || !this.parent.isVisible) { return null; } // Ensure the parent box sizing data is computed. if (!this._box) { this._box = ElementExt.boxSizing(this.parent.node); } // Convert from client to local coordinates. let rect = this.parent.node.getBoundingClientRect(); let x = clientX - rect.left - this._box.borderLeft; let y = clientY - rect.top - this._box.borderTop; // Find the tab layout node at the local position. let tabNode = this._root.hitTestTabNodes(x, y); // Bail if a tab layout node was not found. if (!tabNode) { return null; } // Extract the data from the tab node. let { tabBar, top, left, width, height } = tabNode; // Compute the right and bottom edges of the tab area. let borderWidth = this._box.borderLeft + this._box.borderRight; let borderHeight = this._box.borderTop + this._box.borderBottom; let right = rect.width - borderWidth - (left + width); let bottom = rect.height - borderHeight - (top + height); // Return the hit test results. return { tabBar, x, y, top, left, right, bottom, width, height }; } /** * Perform layout initialization which requires the parent widget. */ protected init(): void { // Perform superclass initialization. super.init(); // Attach each widget to the parent. each(this, widget => { this.attachWidget(widget); }); // Attach each handle to the parent. each(this.handles(), handle => { this.parent!.node.appendChild(handle); }); // Post a fit request for the parent widget. this.parent!.fit(); } /** * Attach the widget to the layout parent widget. * * @param widget - The widget to attach to the parent. * * #### Notes * This is a no-op if the widget is already attached. */ protected attachWidget(widget: Widget): void { // Do nothing if the widget is already attached. if (this.parent!.node === widget.node.parentNode) { return; } // Create the layout item for the widget. this._items.set(widget, new LayoutItem(widget)); // Send a `'before-attach'` message if the parent is attached. if (this.parent!.isAttached) { MessageLoop.sendMessage(widget, Widget.Msg.BeforeAttach); } // Add the widget's node to the parent. this.parent!.node.appendChild(widget.node); // Send an `'after-attach'` message if the parent is attached. if (this.parent!.isAttached) { MessageLoop.sendMessage(widget, Widget.Msg.AfterAttach); } } /** * Detach the widget from the layout parent widget. * * @param widget - The widget to detach from the parent. * * #### Notes * This is a no-op if the widget is not attached. */ protected detachWidget(widget: Widget): void { // Do nothing if the widget is not attached. if (this.parent!.node !== widget.node.parentNode) { return; } // Send a `'before-detach'` message if the parent is attached. if (this.parent!.isAttached) { MessageLoop.sendMessage(widget, Widget.Msg.BeforeDetach); } // Remove the widget's node from the parent. this.parent!.node.removeChild(widget.node); // Send an `'after-detach'` message if the parent is attached. if (this.parent!.isAttached) { MessageLoop.sendMessage(widget, Widget.Msg.AfterDetach); } // Delete the layout item for the widget. let item = this._items.get(widget); if (item) { this._items.delete(widget); item.dispose(); } } /** * A message handler invoked on a `'before-show'` message. */ protected onBeforeShow(msg: Message): void { super.onBeforeShow(msg); this.parent!.update(); } /** * A message handler invoked on a `'before-attach'` message. */ protected onBeforeAttach(msg: Message): void { super.onBeforeAttach(msg); this.parent!.fit(); } /** * A message handler invoked on a `'child-shown'` message. */ protected onChildShown(msg: Widget.ChildMessage): void { this.parent!.fit(); } /** * A message handler invoked on a `'child-hidden'` message. */ protected onChildHidden(msg: Widget.ChildMessage): void { this.parent!.fit(); } /** * A message handler invoked on a `'resize'` message. */ protected onResize(msg: Widget.ResizeMessage): void { if (this.parent!.isVisible) { this._update(msg.width, msg.height); } } /** * A message handler invoked on an `'update-request'` message. */ protected onUpdateRequest(msg: Message): void { if (this.parent!.isVisible) { this._update(-1, -1); } } /** * A message handler invoked on a `'fit-request'` message. */ protected onFitRequest(msg: Message): void { if (this.parent!.isAttached) { this._fit(); } } /** * Remove the specified widget from the layout structure. * * #### Notes * This is a no-op if the widget is not in the layout tree. * * This does not detach the widget from the parent node. */ private _removeWidget(widget: Widget): void { // Bail early if there is no layout root. if (!this._root) { return; } // Find the tab node which contains the given widget. let tabNode = this._root.findTabNode(widget); // Bail early if the tab node is not found. if (!tabNode) { return; } Private.removeAria(widget); // If there are multiple tabs, just remove the widget's tab. if (tabNode.tabBar.titles.length > 1) { tabNode.tabBar.removeTab(widget.title); if ( this._hiddenMode === Widget.HiddenMode.Scale && tabNode.tabBar.titles.length == 1 ) { const existingWidget = tabNode.tabBar.titles[0].owner; existingWidget.hiddenMode = Widget.HiddenMode.Display; } return; } // Otherwise, the tab node needs to be removed... // Dispose the tab bar. tabNode.tabBar.dispose(); // Handle the case where the tab node is the root. if (this._root === tabNode) { this._root = null; return; } // Otherwise, remove the tab node from its parent... // Prevent widget resizing unless needed. this._root.holdAllSizes(); // Clear the parent reference on the tab node. let splitNode = tabNode.parent!; tabNode.parent = null; // Remove the tab node from its parent split node. let i = ArrayExt.removeFirstOf(splitNode.children, tabNode); let handle = ArrayExt.removeAt(splitNode.handles, i)!; ArrayExt.removeAt(splitNode.sizers, i); // Remove the handle from its parent DOM node. if (handle.parentNode) { handle.parentNode.removeChild(handle); } // If there are multiple children, just update the handles. if (splitNode.children.length > 1) { splitNode.syncHandles(); return; } // Otherwise, the split node also needs to be removed... // Clear the parent reference on the split node. let maybeParent = splitNode.parent; splitNode.parent = null; // Lookup the remaining child node and handle. let childNode = splitNode.children[0]; let childHandle = splitNode.handles[0]; // Clear the split node data. splitNode.children.length = 0; splitNode.handles.length = 0; splitNode.sizers.length = 0; // Remove the child handle from its parent node. if (childHandle.parentNode) { childHandle.parentNode.removeChild(childHandle); } // Handle the case where the split node is the root. if (this._root === splitNode) { childNode.parent = null; this._root = childNode; return; } // Otherwise, move the child node to the parent node... let parentNode = maybeParent!; // Lookup the index of the split node. let j = parentNode.children.indexOf(splitNode); // Handle the case where the child node is a tab node. if (childNode instanceof Private.TabLayoutNode) { childNode.parent = parentNode; parentNode.children[j] = childNode; return; } // Remove the split data from the parent. let splitHandle = ArrayExt.removeAt(parentNode.handles, j)!; ArrayExt.removeAt(parentNode.children, j); ArrayExt.removeAt(parentNode.sizers, j); // Remove the handle from its parent node. if (splitHandle.parentNode) { splitHandle.parentNode.removeChild(splitHandle); } // The child node and the split parent node will have the same // orientation. Merge the grand-children with the parent node. for (let i = 0, n = childNode.children.length; i < n; ++i) { let gChild = childNode.children[i]; let gHandle = childNode.handles[i]; let gSizer = childNode.sizers[i]; ArrayExt.insert(parentNode.children, j + i, gChild); ArrayExt.insert(parentNode.handles, j + i, gHandle); ArrayExt.insert(parentNode.sizers, j + i, gSizer); gChild.parent = parentNode; } // Clear the child node. childNode.children.length = 0; childNode.handles.length = 0; childNode.sizers.length = 0; childNode.parent = null; // Sync the handles on the parent node. parentNode.syncHandles(); } /** * Insert a widget next to an existing tab. * * #### Notes * This does not attach the widget to the parent widget. */ private _insertTab( widget: Widget, ref: Widget | null, refNode: Private.TabLayoutNode | null, after: boolean ): void { // Do nothing if the tab is inserted next to itself. if (widget === ref) { return; } // Create the root if it does not exist. if (!this._root) { let tabNode = new Private.TabLayoutNode(this._createTabBar()); tabNode.tabBar.addTab(widget.title); this._root = tabNode; Private.addAria(widget, tabNode.tabBar); return; } // Use the first tab node as the ref node if needed. if (!refNode) { refNode = this._root.findFirstTabNode()!; } // If the widget is not contained in the ref node, ensure it is // removed from the layout and hidden before being added again. if (refNode.tabBar.titles.indexOf(widget.title) === -1) { this._removeWidget(widget); widget.hide(); } // Lookup the target index for inserting the tab. let index: number; if (ref) { index = refNode.tabBar.titles.indexOf(ref.title); } else { index = refNode.tabBar.currentIndex; } // Using transform create an additional layer in the pixel pipeline // to limit the number of layer, it is set only if there is more than one widget. if ( this._hiddenMode === Widget.HiddenMode.Scale && refNode.tabBar.titles.length > 0 ) { if (refNode.tabBar.titles.length == 1) { const existingWidget = refNode.tabBar.titles[0].owner; existingWidget.hiddenMode = Widget.HiddenMode.Scale; } widget.hiddenMode = Widget.HiddenMode.Scale; } else { widget.hiddenMode = Widget.HiddenMode.Display; } // Insert the widget's tab relative to the target index. refNode.tabBar.insertTab(index + (after ? 1 : 0), widget.title); Private.addAria(widget, refNode.tabBar); } /** * Insert a widget as a new split area. * * #### Notes * This does not attach the widget to the parent widget. */ private _insertSplit( widget: Widget, ref: Widget | null, refNode: Private.TabLayoutNode | null, orientation: Private.Orientation, after: boolean ): void { // Do nothing if there is no effective split. if (widget === ref && refNode && refNode.tabBar.titles.length === 1) { return; } // Ensure the widget is removed from the current layout. this._removeWidget(widget); // Create the tab layout node to hold the widget. let tabNode = new Private.TabLayoutNode(this._createTabBar()); tabNode.tabBar.addTab(widget.title); Private.addAria(widget, tabNode.tabBar); // Set the root if it does not exist. if (!this._root) { this._root = tabNode; return; } // If the ref node parent is null, split the root. if (!refNode || !refNode.parent) { // Ensure the root is split with the correct orientation. let root = this._splitRoot(orientation); // Determine the insert index for the new tab node. let i = after ? root.children.length : 0; // Normalize the split node. root.normalizeSizes(); // Create the sizer for new tab node. let sizer = Private.createSizer(refNode ? 1 : Private.GOLDEN_RATIO); // Insert the tab node sized to the golden ratio. ArrayExt.insert(root.children, i, tabNode); ArrayExt.insert(root.sizers, i, sizer); ArrayExt.insert(root.handles, i, this._createHandle()); tabNode.parent = root; // Re-normalize the split node to maintain the ratios. root.normalizeSizes(); // Finally, synchronize the visibility of the handles. root.syncHandles(); return; } // Lookup the split node for the ref widget. let splitNode = refNode.parent; // If the split node already had the correct orientation, // the widget can be inserted into the split node directly. if (splitNode.orientation === orientation) { // Find the index of the ref node. let i = splitNode.children.indexOf(refNode); // Normalize the split node. splitNode.normalizeSizes(); // Consume half the space for the insert location. let s = (splitNode.sizers[i].sizeHint /= 2); // Insert the tab node sized to the other half. let j = i + (after ? 1 : 0); ArrayExt.insert(splitNode.children, j, tabNode); ArrayExt.insert(splitNode.sizers, j, Private.createSizer(s)); ArrayExt.insert(splitNode.handles, j, this._createHandle()); tabNode.parent = splitNode; // Finally, synchronize the visibility of the handles. splitNode.syncHandles(); return; } // Remove the ref node from the split node. let i = ArrayExt.removeFirstOf(splitNode.children, refNode); // Create a new normalized split node for the children. let childNode = new Private.SplitLayoutNode(orientation); childNode.normalized = true; // Add the ref node sized to half the space. childNode.children.push(refNode); childNode.sizers.push(Private.createSizer(0.5)); childNode.handles.push(this._createHandle()); refNode.parent = childNode; // Add the tab node sized to the other half. let j = after ? 1 : 0; ArrayExt.insert(childNode.children, j, tabNode); ArrayExt.insert(childNode.sizers, j, Private.createSizer(0.5)); ArrayExt.insert(childNode.handles, j, this._createHandle()); tabNode.parent = childNode; // Synchronize the visibility of the handles. childNode.syncHandles(); // Finally, add the new child node to the original split node. ArrayExt.insert(splitNode.children, i, childNode); childNode.parent = splitNode; } /** * Ensure the root is a split node with the given orientation. */ private _splitRoot( orientation: Private.Orientation ): Private.SplitLayoutNode { // Bail early if the root already meets the requirements. let oldRoot = this._root; if (oldRoot instanceof Private.SplitLayoutNode) { if (oldRoot.orientation === orientation) { return oldRoot; } } // Create a new root node with the specified orientation. let newRoot = (this._root = new Private.SplitLayoutNode(orientation)); // Add the old root to the new root. if (oldRoot) { newRoot.children.push(oldRoot); newRoot.sizers.push(Private.createSizer(0)); newRoot.handles.push(this._createHandle()); oldRoot.parent = newRoot; } // Return the new root as a convenience. return newRoot; } /** * Fit the layout to the total size required by the widgets. */ private _fit(): void { // Set up the computed minimum size. let minW = 0; let minH = 0; // Update the size limits for the layout tree. if (this._root) { let limits = this._root.fit(this._spacing, this._items); minW = limits.minWidth; minH = limits.minHeight; } // Update the box sizing and add it to the computed min size. let box = (this._box = ElementExt.boxSizing(this.parent!.node)); minW += box.horizontalSum; minH += box.verticalSum; // Update the parent's min size constraints. let style = this.parent!.node.style; style.minWidth = `${minW}px`; style.minHeight = `${minH}px`; // Set the dirty flag to ensure only a single update occurs. this._dirty = true; // Notify the ancestor that it should fit immediately. This may // cause a resize of the parent, fulfilling the required update. if (this.parent!.parent) { MessageLoop.sendMessage(this.parent!.parent!, Widget.Msg.FitRequest); } // If the dirty flag is still set, the parent was not resized. // Trigger the required update on the parent widget immediately. if (this._dirty) { MessageLoop.sendMessage(this.parent!, Widget.Msg.UpdateRequest); } } /** * Update the layout position and size of the widgets. * * The parent offset dimensions should be `-1` if unknown. */ private _update(offsetWidth: number, offsetHeight: number): void { // Clear the dirty flag to indicate the update occurred. this._dirty = false; // Bail early if there is no root layout node. if (!this._root) { return; } // Measure the parent if the offset dimensions are unknown. if (offsetWidth < 0) { offsetWidth = this.parent!.node.offsetWidth; } if (offsetHeight < 0) { offsetHeight = this.parent!.node.offsetHeight; } // Ensure the parent box sizing data is computed. if (!this._box) { this._box = ElementExt.boxSizing(this.parent!.node); } // Compute the actual layout bounds adjusted for border and padding. let x = this._box.paddingTop; let y = this._box.paddingLeft; let width = offsetWidth - this._box.horizontalSum; let height = offsetHeight - this._box.verticalSum; // Update the geometry of the layout tree. this._root.update(x, y, width, height, this._spacing, this._items); } /** * Create a new tab bar for use by the dock layout. * * #### Notes * The tab bar will be attached to the parent if it exists. */ private _createTabBar(): TabBar { // Create the tab bar using the renderer. let tabBar = this.renderer.createTabBar(); // Enforce necessary tab bar behavior. tabBar.orientation = 'horizontal'; // Reparent and attach the tab bar to the parent if possible. if (this.parent) { tabBar.parent = this.parent; this.attachWidget(tabBar); } // Return the initialized tab bar. return tabBar; } /** * Create a new handle for the dock layout. * * #### Notes * The handle will be attached to the parent if it exists. */ private _createHandle(): HTMLDivElement { // Create the handle using the renderer. let handle = this.renderer.createHandle(); // Initialize the handle layout behavior. let style = handle.style; style.position = 'absolute'; style.top = '0'; style.left = '0'; style.width = '0'; style.height = '0'; // Attach the handle to the parent if it exists. if (this.parent) { this.parent.node.appendChild(handle); } // Return the initialized handle. return handle; } private _spacing = 4; private _dirty = false; private _root: Private.LayoutNode | null = null; private _box: ElementExt.IBoxSizing | null = null; private _hiddenMode: Widget.HiddenMode; private _items: Private.ItemMap = new Map(); } /** * The namespace for the `DockLayout` class statics. */ export namespace DockLayout { /** * An options object for creating a dock layout. */ export interface IOptions { /** * The method for hiding widgets. * * The default is `Widget.HiddenMode.Display`. */ hiddenMode?: Widget.HiddenMode; /** * The renderer to use for the dock layout. */ renderer: IRenderer; /** * The spacing between items in the layout. * * The default is `4`. */ spacing?: number; } /** * A renderer for use with a dock layout. */ export interface IRenderer { /** * Create a new tab bar for use with a dock layout. * * @returns A new tab bar for a dock layout. */ createTabBar(): TabBar; /** * Create a new handle node for use with a dock layout. * * @returns A new handle node for a dock layout. */ createHandle(): HTMLDivElement; } /** * A type alias for the supported insertion modes. * * An insert mode is used to specify how a widget should be added * to the dock layout relative to a reference widget. */ export type InsertMode = | /** * The area to the top of the reference widget. * * The widget will be inserted just above the reference widget. * * If the reference widget is null or invalid, the widget will be * inserted at the top edge of the dock layout. */ 'split-top' /** * The area to the left of the reference widget. * * The widget will be inserted just left of the reference widget. * * If the reference widget is null or invalid, the widget will be * inserted at the left edge of the dock layout. */ | 'split-left' /** * The area to the right of the reference widget. * * The widget will be inserted just right of the reference widget. * * If the reference widget is null or invalid, the widget will be * inserted at the right edge of the dock layout. */ | 'split-right' /** * The area to the bottom of the reference widget. * * The widget will be inserted just below the reference widget. * * If the reference widget is null or invalid, the widget will be * inserted at the bottom edge of the dock layout. */ | 'split-bottom' /** * The tab position before the reference widget. * * The widget will be added as a tab before the reference widget. * * If the reference widget is null or invalid, a sensible default * will be used. */ | 'tab-before' /** * The tab position after the reference widget. * * The widget will be added as a tab after the reference widget. * * If the reference widget is null or invalid, a sensible default * will be used. */ | 'tab-after'; /** * An options object for adding a widget to the dock layout. */ export interface IAddOptions { /** * The insertion mode for adding the widget. * * The default is `'tab-after'`. */ mode?: InsertMode; /** * The reference widget for the insert location. * * The default is `null`. */ ref?: Widget | null; } /** * A layout config object for a tab area. */ export interface ITabAreaConfig { /** * The discriminated type of the config object. */ type: 'tab-area'; /** * The widgets contained in the tab area. */ widgets: Widget[]; /** * The index of the selected tab. */ currentIndex: number; } /** * A layout config object for a split area. */ export interface ISplitAreaConfig { /** * The discriminated type of the config object. */ type: 'split-area'; /** * The orientation of the split area. */ orientation: 'horizontal' | 'vertical'; /** * The children in the split area. */ children: AreaConfig[]; /** * The relative sizes of the children. */ sizes: number[]; } /** * A type alias for a general area config. */ export type AreaConfig = ITabAreaConfig | ISplitAreaConfig; /** * A dock layout configuration object. */ export interface ILayoutConfig { /** * The layout config for the main dock area. */ main: AreaConfig | null; } /** * An object which represents the geometry of a tab area. */ export interface ITabAreaGeometry { /** * The tab bar for the tab area. */ tabBar: TabBar; /** * The local X position of the hit test in the dock area. * * #### Notes * This is the distance from the left edge of the layout parent * widget, to the local X coordinate of the hit test query. */ x: number; /** * The local Y position of the hit test in the dock area. * * #### Notes * This is the distance from the top edge of the layout parent * widget, to the local Y coordinate of the hit test query. */ y: number; /** * The local coordinate of the top edge of the tab area. * * #### Notes * This is the distance from the top edge of the layout parent * widget, to the top edge of the tab area. */ top: number; /** * The local coordinate of the left edge of the tab area. * * #### Notes * This is the distance from the left edge of the layout parent * widget, to the left edge of the tab area. */ left: number; /** * The local coordinate of the right edge of the tab area. * * #### Notes * This is the distance from the right edge of the layout parent * widget, to the right edge of the tab area. */ right: number; /** * The local coordinate of the bottom edge of the tab area. * * #### Notes * This is the distance from the bottom edge of the layout parent * widget, to the bottom edge of the tab area. */ bottom: number; /** * The width of the tab area. * * #### Notes * This is total width allocated for the tab area. */ width: number; /** * The height of the tab area. * * #### Notes * This is total height allocated for the tab area. */ height: number; } } /** * The namespace for the module implementation details. */ namespace Private { /** * A fraction used for sizing root panels; ~= `1 / golden_ratio`. */ export const GOLDEN_RATIO = 0.618; /** * A type alias for a dock layout node. */ export type LayoutNode = TabLayoutNode | SplitLayoutNode; /** * A type alias for the orientation of a split layout node. */ export type Orientation = 'horizontal' | 'vertical'; /** * A type alias for a layout item map. */ export type ItemMap = Map; /** * Create a box sizer with an initial size hint. */ export function createSizer(hint: number): BoxSizer { let sizer = new BoxSizer(); sizer.sizeHint = hint; sizer.size = hint; return sizer; } /** * Normalize an area config object and collect the visited widgets. */ export function normalizeAreaConfig( config: DockLayout.AreaConfig, widgetSet: Set ): DockLayout.AreaConfig | null { let result: DockLayout.AreaConfig | null; if (config.type === 'tab-area') { result = normalizeTabAreaConfig(config, widgetSet); } else { result = normalizeSplitAreaConfig(config, widgetSet); } return result; } /** * Convert a normalized area config into a layout tree. */ export function realizeAreaConfig( config: DockLayout.AreaConfig, renderer: DockLayout.IRenderer ): LayoutNode { let node: LayoutNode; if (config.type === 'tab-area') { node = realizeTabAreaConfig(config, renderer); } else { node = realizeSplitAreaConfig(config, renderer); } return node; } /** * A layout node which holds the data for a tabbed area. */ export class TabLayoutNode { /** * Construct a new tab layout node. * * @param tabBar - The tab bar to use for the layout node. */ constructor(tabBar: TabBar) { let tabSizer = new BoxSizer(); let widgetSizer = new BoxSizer(); tabSizer.stretch = 0; widgetSizer.stretch = 1; this.tabBar = tabBar; this.sizers = [tabSizer, widgetSizer]; } /** * The parent of the layout node. */ parent: SplitLayoutNode | null = null; /** * The tab bar for the layout node. */ readonly tabBar: TabBar; /** * The sizers for the layout node. */ readonly sizers: [BoxSizer, BoxSizer]; /** * The most recent value for the `top` edge of the layout box. */ get top(): number { return this._top; } /** * The most recent value for the `left` edge of the layout box. */ get left(): number { return this._left; } /** * The most recent value for the `width` of the layout box. */ get width(): number { return this._width; } /** * The most recent value for the `height` of the layout box. */ get height(): number { return this._height; } /** * Create an iterator for all widgets in the layout tree. */ iterAllWidgets(): IIterator { return chain(once(this.tabBar), this.iterUserWidgets()); } /** * Create an iterator for the user widgets in the layout tree. */ iterUserWidgets(): IIterator { return map(this.tabBar.titles, title => title.owner); } /** * Create an iterator for the selected widgets in the layout tree. */ iterSelectedWidgets(): IIterator { let title = this.tabBar.currentTitle; return title ? once(title.owner) : empty(); } /** * Create an iterator for the tab bars in the layout tree. */ iterTabBars(): IIterator> { return once(this.tabBar); } /** * Create an iterator for the handles in the layout tree. */ iterHandles(): IIterator { return empty(); } /** * Find the tab layout node which contains the given widget. */ findTabNode(widget: Widget): TabLayoutNode | null { return this.tabBar.titles.indexOf(widget.title) !== -1 ? this : null; } /** * Find the split layout node which contains the given handle. */ findSplitNode( handle: HTMLDivElement ): { index: number; node: SplitLayoutNode } | null { return null; } /** * Find the first tab layout node in a layout tree. */ findFirstTabNode(): TabLayoutNode | null { return this; } /** * Find the tab layout node which contains the local point. */ hitTestTabNodes(x: number, y: number): TabLayoutNode | null { if (x < this._left || x >= this._left + this._width) { return null; } if (y < this._top || y >= this._top + this._height) { return null; } return this; } /** * Create a configuration object for the layout tree. */ createConfig(): DockLayout.ITabAreaConfig { let widgets = this.tabBar.titles.map(title => title.owner); let currentIndex = this.tabBar.currentIndex; return { type: 'tab-area', widgets, currentIndex }; } /** * Recursively hold all of the sizes in the layout tree. * * This ignores the sizers of tab layout nodes. */ holdAllSizes(): void { return; } /** * Fit the layout tree. */ fit(spacing: number, items: ItemMap): ElementExt.ISizeLimits { // Set up the limit variables. let minWidth = 0; let minHeight = 0; let maxWidth = Infinity; let maxHeight = Infinity; // Lookup the tab bar layout item. let tabBarItem = items.get(this.tabBar); // Lookup the widget layout item. let current = this.tabBar.currentTitle; let widgetItem = current ? items.get(current.owner) : undefined; // Lookup the tab bar and widget sizers. let [tabBarSizer, widgetSizer] = this.sizers; // Update the tab bar limits. if (tabBarItem) { tabBarItem.fit(); } // Update the widget limits. if (widgetItem) { widgetItem.fit(); } // Update the results and sizer for the tab bar. if (tabBarItem && !tabBarItem.isHidden) { minWidth = Math.max(minWidth, tabBarItem.minWidth); minHeight += tabBarItem.minHeight; tabBarSizer.minSize = tabBarItem.minHeight; tabBarSizer.maxSize = tabBarItem.maxHeight; } else { tabBarSizer.minSize = 0; tabBarSizer.maxSize = 0; } // Update the results and sizer for the current widget. if (widgetItem && !widgetItem.isHidden) { minWidth = Math.max(minWidth, widgetItem.minWidth); minHeight += widgetItem.minHeight; widgetSizer.minSize = widgetItem.minHeight; widgetSizer.maxSize = Infinity; } else { widgetSizer.minSize = 0; widgetSizer.maxSize = Infinity; } // Return the computed size limits for the layout node. return { minWidth, minHeight, maxWidth, maxHeight }; } /** * Update the layout tree. */ update( left: number, top: number, width: number, height: number, spacing: number, items: ItemMap ): void { // Update the layout box values. this._top = top; this._left = left; this._width = width; this._height = height; // Lookup the tab bar layout item. let tabBarItem = items.get(this.tabBar); // Lookup the widget layout item. let current = this.tabBar.currentTitle; let widgetItem = current ? items.get(current.owner) : undefined; // Distribute the layout space to the sizers. BoxEngine.calc(this.sizers, height); // Update the tab bar item using the computed size. if (tabBarItem && !tabBarItem.isHidden) { let size = this.sizers[0].size; tabBarItem.update(left, top, width, size); top += size; } // Layout the widget using the computed size. if (widgetItem && !widgetItem.isHidden) { let size = this.sizers[1].size; widgetItem.update(left, top, width, size); } } private _top = 0; private _left = 0; private _width = 0; private _height = 0; } /** * A layout node which holds the data for a split area. */ export class SplitLayoutNode { /** * Construct a new split layout node. * * @param orientation - The orientation of the node. */ constructor(orientation: Orientation) { this.orientation = orientation; } /** * The parent of the layout node. */ parent: SplitLayoutNode | null = null; /** * Whether the sizers have been normalized. */ normalized = false; /** * The orientation of the node. */ readonly orientation: Orientation; /** * The child nodes for the split node. */ readonly children: LayoutNode[] = []; /** * The box sizers for the layout children. */ readonly sizers: BoxSizer[] = []; /** * The handles for the layout children. */ readonly handles: HTMLDivElement[] = []; /** * Create an iterator for all widgets in the layout tree. */ iterAllWidgets(): IIterator { let children = map(this.children, child => child.iterAllWidgets()); return new ChainIterator(children); } /** * Create an iterator for the user widgets in the layout tree. */ iterUserWidgets(): IIterator { let children = map(this.children, child => child.iterUserWidgets()); return new ChainIterator(children); } /** * Create an iterator for the selected widgets in the layout tree. */ iterSelectedWidgets(): IIterator { let children = map(this.children, child => child.iterSelectedWidgets()); return new ChainIterator(children); } /** * Create an iterator for the tab bars in the layout tree. */ iterTabBars(): IIterator> { let children = map(this.children, child => child.iterTabBars()); return new ChainIterator>(children); } /** * Create an iterator for the handles in the layout tree. */ iterHandles(): IIterator { let children = map(this.children, child => child.iterHandles()); return chain(this.handles, new ChainIterator(children)); } /** * Find the tab layout node which contains the given widget. */ findTabNode(widget: Widget): TabLayoutNode | null { for (let i = 0, n = this.children.length; i < n; ++i) { let result = this.children[i].findTabNode(widget); if (result) { return result; } } return null; } /** * Find the split layout node which contains the given handle. */ findSplitNode( handle: HTMLDivElement ): { index: number; node: SplitLayoutNode } | null { let index = this.handles.indexOf(handle); if (index !== -1) { return { index, node: this }; } for (let i = 0, n = this.children.length; i < n; ++i) { let result = this.children[i].findSplitNode(handle); if (result) { return result; } } return null; } /** * Find the first tab layout node in a layout tree. */ findFirstTabNode(): TabLayoutNode | null { if (this.children.length === 0) { return null; } return this.children[0].findFirstTabNode(); } /** * Find the tab layout node which contains the local point. */ hitTestTabNodes(x: number, y: number): TabLayoutNode | null { for (let i = 0, n = this.children.length; i < n; ++i) { let result = this.children[i].hitTestTabNodes(x, y); if (result) { return result; } } return null; } /** * Create a configuration object for the layout tree. */ createConfig(): DockLayout.ISplitAreaConfig { let orientation = this.orientation; let sizes = this.createNormalizedSizes(); let children = this.children.map(child => child.createConfig()); return { type: 'split-area', orientation, children, sizes }; } /** * Sync the visibility and orientation of the handles. */ syncHandles(): void { each(this.handles, (handle, i) => { handle.setAttribute('data-orientation', this.orientation); if (i === this.handles.length - 1) { handle.classList.add('lm-mod-hidden'); /* */ handle.classList.add('p-mod-hidden'); /* */ } else { handle.classList.remove('lm-mod-hidden'); /* */ handle.classList.remove('p-mod-hidden'); /* */ } }); } /** * Hold the current sizes of the box sizers. * * This sets the size hint of each sizer to its current size. */ holdSizes(): void { each(this.sizers, sizer => { sizer.sizeHint = sizer.size; }); } /** * Recursively hold all of the sizes in the layout tree. * * This ignores the sizers of tab layout nodes. */ holdAllSizes(): void { each(this.children, child => child.holdAllSizes()); this.holdSizes(); } /** * Normalize the sizes of the split layout node. */ normalizeSizes(): void { // Bail early if the sizers are empty. let n = this.sizers.length; if (n === 0) { return; } // Hold the current sizes of the sizers. this.holdSizes(); // Compute the sum of the sizes. let sum = reduce(this.sizers, (v, sizer) => v + sizer.sizeHint, 0); // Normalize the sizes based on the sum. if (sum === 0) { each(this.sizers, sizer => { sizer.size = sizer.sizeHint = 1 / n; }); } else { each(this.sizers, sizer => { sizer.size = sizer.sizeHint /= sum; }); } // Mark the sizes as normalized. this.normalized = true; } /** * Snap the normalized sizes of the split layout node. */ createNormalizedSizes(): number[] { // Bail early if the sizers are empty. let n = this.sizers.length; if (n === 0) { return []; } // Grab the current sizes of the sizers. let sizes = this.sizers.map(sizer => sizer.size); // Compute the sum of the sizes. let sum = reduce(sizes, (v, size) => v + size, 0); // Normalize the sizes based on the sum. if (sum === 0) { each(sizes, (size, i) => { sizes[i] = 1 / n; }); } else { each(sizes, (size, i) => { sizes[i] = size / sum; }); } // Return the normalized sizes. return sizes; } /** * Fit the layout tree. */ fit(spacing: number, items: ItemMap): ElementExt.ISizeLimits { // Compute the required fixed space. let horizontal = this.orientation === 'horizontal'; let fixed = Math.max(0, this.children.length - 1) * spacing; // Set up the limit variables. let minWidth = horizontal ? fixed : 0; let minHeight = horizontal ? 0 : fixed; let maxWidth = Infinity; let maxHeight = Infinity; // Fit the children and update the limits. for (let i = 0, n = this.children.length; i < n; ++i) { let limits = this.children[i].fit(spacing, items); if (horizontal) { minHeight = Math.max(minHeight, limits.minHeight); minWidth += limits.minWidth; this.sizers[i].minSize = limits.minWidth; } else { minWidth = Math.max(minWidth, limits.minWidth); minHeight += limits.minHeight; this.sizers[i].minSize = limits.minHeight; } } // Return the computed limits for the layout node. return { minWidth, minHeight, maxWidth, maxHeight }; } /** * Update the layout tree. */ update( left: number, top: number, width: number, height: number, spacing: number, items: ItemMap ): void { // Compute the available layout space. let horizontal = this.orientation === 'horizontal'; let fixed = Math.max(0, this.children.length - 1) * spacing; let space = Math.max(0, (horizontal ? width : height) - fixed); // De-normalize the sizes if needed. if (this.normalized) { each(this.sizers, sizer => { sizer.sizeHint *= space; }); this.normalized = false; } // Distribute the layout space to the sizers. BoxEngine.calc(this.sizers, space); // Update the geometry of the child nodes and handles. for (let i = 0, n = this.children.length; i < n; ++i) { let child = this.children[i]; let size = this.sizers[i].size; let handleStyle = this.handles[i].style; if (horizontal) { child.update(left, top, size, height, spacing, items); left += size; handleStyle.top = `${top}px`; handleStyle.left = `${left}px`; handleStyle.width = `${spacing}px`; handleStyle.height = `${height}px`; left += spacing; } else { child.update(left, top, width, size, spacing, items); top += size; handleStyle.top = `${top}px`; handleStyle.left = `${left}px`; handleStyle.width = `${width}px`; handleStyle.height = `${spacing}px`; top += spacing; } } } } export function addAria(widget: Widget, tabBar: TabBar) { widget.node.setAttribute('role', 'tabpanel'); let renderer = tabBar.renderer; if (renderer instanceof TabBar.Renderer) { let tabId = renderer.createTabKey({ title: widget.title, current: false, zIndex: 0 }); widget.node.setAttribute('aria-labelledby', tabId); } } export function removeAria(widget: Widget) { widget.node.removeAttribute('role'); widget.node.removeAttribute('aria-labelledby'); } /** * Normalize a tab area config and collect the visited widgets. */ function normalizeTabAreaConfig( config: DockLayout.ITabAreaConfig, widgetSet: Set ): DockLayout.ITabAreaConfig | null { // Bail early if there is no content. if (config.widgets.length === 0) { return null; } // Setup the filtered widgets array. let widgets: Widget[] = []; // Filter the config for unique widgets. each(config.widgets, widget => { if (!widgetSet.has(widget)) { widgetSet.add(widget); widgets.push(widget); } }); // Bail if there are no effective widgets. if (widgets.length === 0) { return null; } // Normalize the current index. let index = config.currentIndex; if (index !== -1 && (index < 0 || index >= widgets.length)) { index = 0; } // Return a normalized config object. return { type: 'tab-area', widgets, currentIndex: index }; } /** * Normalize a split area config and collect the visited widgets. */ function normalizeSplitAreaConfig( config: DockLayout.ISplitAreaConfig, widgetSet: Set ): DockLayout.AreaConfig | null { // Set up the result variables. let orientation = config.orientation; let children: DockLayout.AreaConfig[] = []; let sizes: number[] = []; // Normalize the config children. for (let i = 0, n = config.children.length; i < n; ++i) { // Normalize the child config. let child = normalizeAreaConfig(config.children[i], widgetSet); // Ignore an empty child. if (!child) { continue; } // Add the child or hoist its content as appropriate. if (child.type === 'tab-area' || child.orientation !== orientation) { children.push(child); sizes.push(Math.abs(config.sizes[i] || 0)); } else { children.push(...child.children); sizes.push(...child.sizes); } } // Bail if there are no effective children. if (children.length === 0) { return null; } // If there is only one effective child, return that child. if (children.length === 1) { return children[0]; } // Return a normalized config object. return { type: 'split-area', orientation, children, sizes }; } /** * Convert a normalized tab area config into a layout tree. */ function realizeTabAreaConfig( config: DockLayout.ITabAreaConfig, renderer: DockLayout.IRenderer ): TabLayoutNode { // Create the tab bar for the layout node. let tabBar = renderer.createTabBar(); // Hide each widget and add it to the tab bar. each(config.widgets, widget => { widget.hide(); tabBar.addTab(widget.title); Private.addAria(widget, tabBar); }); // Set the current index of the tab bar. tabBar.currentIndex = config.currentIndex; // Return the new tab layout node. return new TabLayoutNode(tabBar); } /** * Convert a normalized split area config into a layout tree. */ function realizeSplitAreaConfig( config: DockLayout.ISplitAreaConfig, renderer: DockLayout.IRenderer ): SplitLayoutNode { // Create the split layout node. let node = new SplitLayoutNode(config.orientation); // Add each child to the layout node. each(config.children, (child, i) => { // Create the child data for the layout node. let childNode = realizeAreaConfig(child, renderer); let sizer = createSizer(config.sizes[i]); let handle = renderer.createHandle(); // Add the child data to the layout node. node.children.push(childNode); node.handles.push(handle); node.sizers.push(sizer); // Update the parent for the child node. childNode.parent = node; }); // Synchronize the handle state for the layout node. node.syncHandles(); // Normalize the sizes for the layout node. node.normalizeSizes(); // Return the new layout node. return node; } } lumino-2021.12.13/packages/widgets/src/dockpanel.ts000066400000000000000000001330741415564225700217540ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { each, find, IIterator, toArray } from '@lumino/algorithm'; import { MimeData } from '@lumino/coreutils'; import { IDisposable } from '@lumino/disposable'; import { ElementExt, Platform } from '@lumino/domutils'; import { Drag, IDragEvent } from '@lumino/dragdrop'; import { ConflatableMessage, Message, MessageLoop } from '@lumino/messaging'; import { AttachedProperty } from '@lumino/properties'; import { ISignal, Signal } from '@lumino/signaling'; import { DockLayout } from './docklayout'; import { TabBar } from './tabbar'; import { Widget } from './widget'; /** * A widget which provides a flexible docking area for widgets. */ export class DockPanel extends Widget { /** * Construct a new dock panel. * * @param options - The options for initializing the panel. */ constructor(options: DockPanel.IOptions = {}) { super(); this.addClass('lm-DockPanel'); /* */ this.addClass('p-DockPanel'); /* */ this._mode = options.mode || 'multiple-document'; this._renderer = options.renderer || DockPanel.defaultRenderer; this._edges = options.edges || Private.DEFAULT_EDGES; if (options.tabsMovable !== undefined) { this._tabsMovable = options.tabsMovable; } if (options.tabsConstrained !== undefined) { this._tabsConstrained = options.tabsConstrained; } if (options.addButtonEnabled !== undefined) { this._addButtonEnabled = options.addButtonEnabled; } // Toggle the CSS mode attribute. this.dataset['mode'] = this._mode; // Create the delegate renderer for the layout. let renderer: DockPanel.IRenderer = { createTabBar: () => this._createTabBar(), createHandle: () => this._createHandle() }; // Set up the dock layout for the panel. this.layout = new DockLayout({ renderer, spacing: options.spacing, hiddenMode: options.hiddenMode }); // Set up the overlay drop indicator. this.overlay = options.overlay || new DockPanel.Overlay(); this.node.appendChild(this.overlay.node); } /** * Dispose of the resources held by the panel. */ dispose(): void { // Ensure the mouse is released. this._releaseMouse(); // Hide the overlay. this.overlay.hide(0); // Cancel a drag if one is in progress. if (this._drag) { this._drag.dispose(); } // Dispose of the base class. super.dispose(); } /** * The method for hiding widgets. */ get hiddenMode(): Widget.HiddenMode { return (this.layout as DockLayout).hiddenMode; } /** * Set the method for hiding widgets. */ set hiddenMode(v: Widget.HiddenMode) { (this.layout as DockLayout).hiddenMode = v; } /** * A signal emitted when the layout configuration is modified. * * #### Notes * This signal is emitted whenever the current layout configuration * may have changed. * * This signal is emitted asynchronously in a collapsed fashion, so * that multiple synchronous modifications results in only a single * emit of the signal. */ get layoutModified(): ISignal { return this._layoutModified; } /** * A signal emitted when the add button on a tab bar is clicked. * */ get addRequested(): ISignal> { return this._addRequested; } /** * The overlay used by the dock panel. */ readonly overlay: DockPanel.IOverlay; /** * The renderer used by the dock panel. */ get renderer(): DockPanel.IRenderer { return (this.layout as DockLayout).renderer; } /** * Get the spacing between the widgets. */ get spacing(): number { return (this.layout as DockLayout).spacing; } /** * Set the spacing between the widgets. */ set spacing(value: number) { (this.layout as DockLayout).spacing = value; } /** * Get the mode for the dock panel. */ get mode(): DockPanel.Mode { return this._mode; } /** * Set the mode for the dock panel. * * #### Notes * Changing the mode is a destructive operation with respect to the * panel's layout configuration. If layout state must be preserved, * save the current layout config before changing the mode. */ set mode(value: DockPanel.Mode) { // Bail early if the mode does not change. if (this._mode === value) { return; } // Update the internal mode. this._mode = value; // Toggle the CSS mode attribute. this.dataset['mode'] = value; // Get the layout for the panel. let layout = this.layout as DockLayout; // Configure the layout for the specified mode. switch (value) { case 'multiple-document': each(layout.tabBars(), tabBar => { tabBar.show(); }); break; case 'single-document': layout.restoreLayout(Private.createSingleDocumentConfig(this)); break; default: throw 'unreachable'; } // Schedule an emit of the layout modified signal. MessageLoop.postMessage(this, Private.LayoutModified); } /** * Whether the tabs can be dragged / moved at runtime. */ get tabsMovable(): boolean { return this._tabsMovable; } /** * Enable / Disable draggable / movable tabs. */ set tabsMovable(value: boolean) { this._tabsMovable = value; each(this.tabBars(), tabbar => { tabbar.tabsMovable = value; }); } /** * Whether the tabs are constrained to their source dock panel */ get tabsConstrained(): boolean { return this._tabsConstrained; } /** * Constrain/Allow tabs to be dragged outside of this dock panel */ set tabsConstrained(value: boolean) { this._tabsConstrained = value; } /** * Whether the add buttons for each tab bar are enabled. */ get addButtonEnabled(): boolean { return this._addButtonEnabled; } /** * Set whether the add buttons for each tab bar are enabled. */ set addButtonEnabled(value: boolean) { this._addButtonEnabled = value; each(this.tabBars(), tabbar => { tabbar.addButtonEnabled = value; }); } /** * Whether the dock panel is empty. */ get isEmpty(): boolean { return (this.layout as DockLayout).isEmpty; } /** * Create an iterator over the user widgets in the panel. * * @returns A new iterator over the user widgets in the panel. * * #### Notes * This iterator does not include the generated tab bars. */ widgets(): IIterator { return (this.layout as DockLayout).widgets(); } /** * Create an iterator over the selected widgets in the panel. * * @returns A new iterator over the selected user widgets. * * #### Notes * This iterator yields the widgets corresponding to the current tab * of each tab bar in the panel. */ selectedWidgets(): IIterator { return (this.layout as DockLayout).selectedWidgets(); } /** * Create an iterator over the tab bars in the panel. * * @returns A new iterator over the tab bars in the panel. * * #### Notes * This iterator does not include the user widgets. */ tabBars(): IIterator> { return (this.layout as DockLayout).tabBars(); } /** * Create an iterator over the handles in the panel. * * @returns A new iterator over the handles in the panel. */ handles(): IIterator { return (this.layout as DockLayout).handles(); } /** * Select a specific widget in the dock panel. * * @param widget - The widget of interest. * * #### Notes * This will make the widget the current widget in its tab area. */ selectWidget(widget: Widget): void { // Find the tab bar which contains the widget. let tabBar = find(this.tabBars(), bar => { return bar.titles.indexOf(widget.title) !== -1; }); // Throw an error if no tab bar is found. if (!tabBar) { throw new Error('Widget is not contained in the dock panel.'); } // Ensure the widget is the current widget. tabBar.currentTitle = widget.title; } /** * Activate a specified widget in the dock panel. * * @param widget - The widget of interest. * * #### Notes * This will select and activate the given widget. */ activateWidget(widget: Widget): void { this.selectWidget(widget); widget.activate(); } /** * Save the current layout configuration of the dock panel. * * @returns A new config object for the current layout state. * * #### Notes * The return value can be provided to the `restoreLayout` method * in order to restore the layout to its current configuration. */ saveLayout(): DockPanel.ILayoutConfig { return (this.layout as DockLayout).saveLayout(); } /** * Restore the layout to a previously saved configuration. * * @param config - The layout configuration to restore. * * #### Notes * Widgets which currently belong to the layout but which are not * contained in the config will be unparented. * * The dock panel automatically reverts to `'multiple-document'` * mode when a layout config is restored. */ restoreLayout(config: DockPanel.ILayoutConfig): void { // Reset the mode. this._mode = 'multiple-document'; // Restore the layout. (this.layout as DockLayout).restoreLayout(config); // Flush the message loop on IE and Edge to prevent flicker. if (Platform.IS_EDGE || Platform.IS_IE) { MessageLoop.flush(); } // Schedule an emit of the layout modified signal. MessageLoop.postMessage(this, Private.LayoutModified); } /** * Add a widget to the dock panel. * * @param widget - The widget to add to the dock panel. * * @param options - The additional options for adding the widget. * * #### Notes * If the panel is in single document mode, the options are ignored * and the widget is always added as tab in the hidden tab bar. */ addWidget(widget: Widget, options: DockPanel.IAddOptions = {}): void { // Add the widget to the layout. if (this._mode === 'single-document') { (this.layout as DockLayout).addWidget(widget); } else { (this.layout as DockLayout).addWidget(widget, options); } // Schedule an emit of the layout modified signal. MessageLoop.postMessage(this, Private.LayoutModified); } /** * Process a message sent to the widget. * * @param msg - The message sent to the widget. */ processMessage(msg: Message): void { if (msg.type === 'layout-modified') { this._layoutModified.emit(undefined); } else { super.processMessage(msg); } } /** * Handle the DOM events for the dock panel. * * @param event - The DOM event sent to the panel. * * #### Notes * This method implements the DOM `EventListener` interface and is * called in response to events on the panel's DOM node. It should * not be called directly by user code. */ handleEvent(event: Event): void { switch (event.type) { case 'lm-dragenter': this._evtDragEnter(event as IDragEvent); break; case 'lm-dragleave': this._evtDragLeave(event as IDragEvent); break; case 'lm-dragover': this._evtDragOver(event as IDragEvent); break; case 'lm-drop': this._evtDrop(event as IDragEvent); break; case 'mousedown': // this._evtMouseDown(event as MouseEvent); break; case 'mousemove': // this._evtMouseMove(event as MouseEvent); break; case 'mouseup': // this._evtMouseUp(event as MouseEvent); break; case 'pointerdown': this._evtMouseDown(event as MouseEvent); break; case 'pointermove': this._evtMouseMove(event as MouseEvent); break; case 'pointerup': this._evtMouseUp(event as MouseEvent); break; case 'keydown': this._evtKeyDown(event as KeyboardEvent); break; case 'contextmenu': event.preventDefault(); event.stopPropagation(); break; } } /** * A message handler invoked on a `'before-attach'` message. */ protected onBeforeAttach(msg: Message): void { this.node.addEventListener('lm-dragenter', this); this.node.addEventListener('lm-dragleave', this); this.node.addEventListener('lm-dragover', this); this.node.addEventListener('lm-drop', this); this.node.addEventListener('mousedown', this); // this.node.addEventListener('pointerdown', this); } /** * A message handler invoked on an `'after-detach'` message. */ protected onAfterDetach(msg: Message): void { this.node.removeEventListener('lm-dragenter', this); this.node.removeEventListener('lm-dragleave', this); this.node.removeEventListener('lm-dragover', this); this.node.removeEventListener('lm-drop', this); this.node.removeEventListener('mousedown', this); // this.node.removeEventListener('pointerdown', this); this._releaseMouse(); } /** * A message handler invoked on a `'child-added'` message. */ protected onChildAdded(msg: Widget.ChildMessage): void { // Ignore the generated tab bars. if (Private.isGeneratedTabBarProperty.get(msg.child)) { return; } // Add the widget class to the child. msg.child.addClass('lm-DockPanel-widget'); /* */ msg.child.addClass('p-DockPanel-widget'); /* */ } /** * A message handler invoked on a `'child-removed'` message. */ protected onChildRemoved(msg: Widget.ChildMessage): void { // Ignore the generated tab bars. if (Private.isGeneratedTabBarProperty.get(msg.child)) { return; } // Remove the widget class from the child. msg.child.removeClass('lm-DockPanel-widget'); /* */ msg.child.removeClass('p-DockPanel-widget'); /* */ // Schedule an emit of the layout modified signal. MessageLoop.postMessage(this, Private.LayoutModified); } /** * Handle the `'lm-dragenter'` event for the dock panel. */ private _evtDragEnter(event: IDragEvent): void { // If the factory mime type is present, mark the event as // handled in order to get the rest of the drag events. if (event.mimeData.hasData('application/vnd.lumino.widget-factory')) { event.preventDefault(); event.stopPropagation(); } } /** * Handle the `'lm-dragleave'` event for the dock panel. */ private _evtDragLeave(event: IDragEvent): void { // Mark the event as handled. event.preventDefault(); event.stopPropagation(); // The new target might be a descendant, so we might still handle the drop. // Hide asynchronously so that if a lm-dragover event bubbles up to us, the // hide is cancelled by the lm-dragover handler's show overlay logic. this.overlay.hide(1); } /** * Handle the `'lm-dragover'` event for the dock panel. */ private _evtDragOver(event: IDragEvent): void { // Mark the event as handled. event.preventDefault(); event.stopPropagation(); // Show the drop indicator overlay and update the drop // action based on the drop target zone under the mouse. if ( (this._tabsConstrained && event.source !== this) || this._showOverlay(event.clientX, event.clientY) === 'invalid' ) { event.dropAction = 'none'; } else { event.dropAction = event.proposedAction; } } /** * Handle the `'lm-drop'` event for the dock panel. */ private _evtDrop(event: IDragEvent): void { // Mark the event as handled. event.preventDefault(); event.stopPropagation(); // Hide the drop indicator overlay. this.overlay.hide(0); // Bail if the proposed action is to do nothing. if (event.proposedAction === 'none') { event.dropAction = 'none'; return; } // Find the drop target under the mouse. let { clientX, clientY } = event; let { zone, target } = Private.findDropTarget( this, clientX, clientY, this._edges ); // Bail if the drop zone is invalid. if (zone === 'invalid') { event.dropAction = 'none'; return; } // Bail if the factory mime type has invalid data. let mimeData = event.mimeData; let factory = mimeData.getData('application/vnd.lumino.widget-factory'); if (typeof factory !== 'function') { event.dropAction = 'none'; return; } // Bail if the factory does not produce a widget. let widget = factory(); if (!(widget instanceof Widget)) { event.dropAction = 'none'; return; } // Bail if the widget is an ancestor of the dock panel. if (widget.contains(this)) { event.dropAction = 'none'; return; } // Find the reference widget for the drop target. let ref = target ? Private.getDropRef(target.tabBar) : null; // Add the widget according to the indicated drop zone. switch (zone) { case 'root-all': this.addWidget(widget); break; case 'root-top': this.addWidget(widget, { mode: 'split-top' }); break; case 'root-left': this.addWidget(widget, { mode: 'split-left' }); break; case 'root-right': this.addWidget(widget, { mode: 'split-right' }); break; case 'root-bottom': this.addWidget(widget, { mode: 'split-bottom' }); break; case 'widget-all': this.addWidget(widget, { mode: 'tab-after', ref }); break; case 'widget-top': this.addWidget(widget, { mode: 'split-top', ref }); break; case 'widget-left': this.addWidget(widget, { mode: 'split-left', ref }); break; case 'widget-right': this.addWidget(widget, { mode: 'split-right', ref }); break; case 'widget-bottom': this.addWidget(widget, { mode: 'split-bottom', ref }); break; case 'widget-tab': this.addWidget(widget, { mode: 'tab-after', ref }); break; default: throw 'unreachable'; } // Accept the proposed drop action. event.dropAction = event.proposedAction; // Activate the dropped widget. this.activateWidget(widget); } /** * Handle the `'keydown'` event for the dock panel. */ private _evtKeyDown(event: KeyboardEvent): void { // Stop input events during drag. event.preventDefault(); event.stopPropagation(); // Release the mouse if `Escape` is pressed. if (event.keyCode === 27) { // Finalize the mouse release. this._releaseMouse(); // Schedule an emit of the layout modified signal. MessageLoop.postMessage(this, Private.LayoutModified); } } /** * Handle the `'mousedown'` event for the dock panel. */ private _evtMouseDown(event: MouseEvent): void { // Do nothing if the left mouse button is not pressed. if (event.button !== 0) { return; } // Find the handle which contains the mouse target, if any. let layout = this.layout as DockLayout; let target = event.target as HTMLElement; let handle = find(layout.handles(), handle => handle.contains(target)); if (!handle) { return; } // Stop the event when a handle is pressed. event.preventDefault(); event.stopPropagation(); // Add the extra document listeners. document.addEventListener('keydown', this, true); document.addEventListener('mouseup', this, true); // document.addEventListener('mousemove', this, true); // document.addEventListener('pointerup', this, true); document.addEventListener('pointermove', this, true); document.addEventListener('contextmenu', this, true); // Compute the offset deltas for the handle press. let rect = handle.getBoundingClientRect(); let deltaX = event.clientX - rect.left; let deltaY = event.clientY - rect.top; // Override the cursor and store the press data. let style = window.getComputedStyle(handle); let override = Drag.overrideCursor(style.cursor!); this._pressData = { handle, deltaX, deltaY, override }; } /** * Handle the `'mousemove'` event for the dock panel. */ private _evtMouseMove(event: MouseEvent): void { // Bail early if no drag is in progress. if (!this._pressData) { return; } // Stop the event when dragging a handle. event.preventDefault(); event.stopPropagation(); // Compute the desired offset position for the handle. let rect = this.node.getBoundingClientRect(); let xPos = event.clientX - rect.left - this._pressData.deltaX; let yPos = event.clientY - rect.top - this._pressData.deltaY; // Set the handle as close to the desired position as possible. let layout = this.layout as DockLayout; layout.moveHandle(this._pressData.handle, xPos, yPos); } /** * Handle the `'mouseup'` event for the dock panel. */ private _evtMouseUp(event: MouseEvent): void { // Do nothing if the left mouse button is not released. if (event.button !== 0) { return; } // Stop the event when releasing a handle. event.preventDefault(); event.stopPropagation(); // Finalize the mouse release. this._releaseMouse(); // Schedule an emit of the layout modified signal. MessageLoop.postMessage(this, Private.LayoutModified); } /** * Release the mouse grab for the dock panel. */ private _releaseMouse(): void { // Bail early if no drag is in progress. if (!this._pressData) { return; } // Clear the override cursor. this._pressData.override.dispose(); this._pressData = null; // Remove the extra document listeners. document.removeEventListener('keydown', this, true); document.removeEventListener('mouseup', this, true); // document.removeEventListener('mousemove', this, true); // document.removeEventListener('pointerup', this, true); document.removeEventListener('pointermove', this, true); document.removeEventListener('contextmenu', this, true); } /** * Show the overlay indicator at the given client position. * * Returns the drop zone at the specified client position. * * #### Notes * If the position is not over a valid zone, the overlay is hidden. */ private _showOverlay(clientX: number, clientY: number): Private.DropZone { // Find the dock target for the given client position. let { zone, target } = Private.findDropTarget( this, clientX, clientY, this._edges ); // If the drop zone is invalid, hide the overlay and bail. if (zone === 'invalid') { this.overlay.hide(100); return zone; } // Setup the variables needed to compute the overlay geometry. let top: number; let left: number; let right: number; let bottom: number; let box = ElementExt.boxSizing(this.node); // TODO cache this? let rect = this.node.getBoundingClientRect(); // Compute the overlay geometry based on the dock zone. switch (zone) { case 'root-all': top = box.paddingTop; left = box.paddingLeft; right = box.paddingRight; bottom = box.paddingBottom; break; case 'root-top': top = box.paddingTop; left = box.paddingLeft; right = box.paddingRight; bottom = rect.height * Private.GOLDEN_RATIO; break; case 'root-left': top = box.paddingTop; left = box.paddingLeft; right = rect.width * Private.GOLDEN_RATIO; bottom = box.paddingBottom; break; case 'root-right': top = box.paddingTop; left = rect.width * Private.GOLDEN_RATIO; right = box.paddingRight; bottom = box.paddingBottom; break; case 'root-bottom': top = rect.height * Private.GOLDEN_RATIO; left = box.paddingLeft; right = box.paddingRight; bottom = box.paddingBottom; break; case 'widget-all': top = target!.top; left = target!.left; right = target!.right; bottom = target!.bottom; break; case 'widget-top': top = target!.top; left = target!.left; right = target!.right; bottom = target!.bottom + target!.height / 2; break; case 'widget-left': top = target!.top; left = target!.left; right = target!.right + target!.width / 2; bottom = target!.bottom; break; case 'widget-right': top = target!.top; left = target!.left + target!.width / 2; right = target!.right; bottom = target!.bottom; break; case 'widget-bottom': top = target!.top + target!.height / 2; left = target!.left; right = target!.right; bottom = target!.bottom; break; case 'widget-tab': const tabHeight = target!.tabBar.node.getBoundingClientRect().height; top = target!.top; left = target!.left; right = target!.right; bottom = target!.bottom + target!.height - tabHeight; break; default: throw 'unreachable'; } // Show the overlay with the computed geometry. this.overlay.show({ top, left, right, bottom }); // Finally, return the computed drop zone. return zone; } /** * Create a new tab bar for use by the panel. */ private _createTabBar(): TabBar { // Create the tab bar. let tabBar = this._renderer.createTabBar(); // Set the generated tab bar property for the tab bar. Private.isGeneratedTabBarProperty.set(tabBar, true); // Hide the tab bar when in single document mode. if (this._mode === 'single-document') { tabBar.hide(); } // Enforce necessary tab bar behavior. // TODO do we really want to enforce *all* of these? tabBar.tabsMovable = this._tabsMovable; tabBar.allowDeselect = false; tabBar.addButtonEnabled = this._addButtonEnabled; tabBar.removeBehavior = 'select-previous-tab'; tabBar.insertBehavior = 'select-tab-if-needed'; // Connect the signal handlers for the tab bar. tabBar.tabMoved.connect(this._onTabMoved, this); tabBar.currentChanged.connect(this._onCurrentChanged, this); tabBar.tabCloseRequested.connect(this._onTabCloseRequested, this); tabBar.tabDetachRequested.connect(this._onTabDetachRequested, this); tabBar.tabActivateRequested.connect(this._onTabActivateRequested, this); tabBar.addRequested.connect(this._onTabAddRequested, this); // Return the initialized tab bar. return tabBar; } /** * Create a new handle for use by the panel. */ private _createHandle(): HTMLDivElement { return this._renderer.createHandle(); } /** * Handle the `tabMoved` signal from a tab bar. */ private _onTabMoved(): void { MessageLoop.postMessage(this, Private.LayoutModified); } /** * Handle the `currentChanged` signal from a tab bar. */ private _onCurrentChanged( sender: TabBar, args: TabBar.ICurrentChangedArgs ): void { // Extract the previous and current title from the args. let { previousTitle, currentTitle } = args; // Hide the previous widget. if (previousTitle) { previousTitle.owner.hide(); } // Show the current widget. if (currentTitle) { currentTitle.owner.show(); } // Flush the message loop on IE and Edge to prevent flicker. if (Platform.IS_EDGE || Platform.IS_IE) { MessageLoop.flush(); } // Schedule an emit of the layout modified signal. MessageLoop.postMessage(this, Private.LayoutModified); } /** * Handle the `addRequested` signal from a tab bar. */ private _onTabAddRequested(sender: TabBar): void { this._addRequested.emit(sender); } /** * Handle the `tabActivateRequested` signal from a tab bar. */ private _onTabActivateRequested( sender: TabBar, args: TabBar.ITabActivateRequestedArgs ): void { args.title.owner.activate(); } /** * Handle the `tabCloseRequested` signal from a tab bar. */ private _onTabCloseRequested( sender: TabBar, args: TabBar.ITabCloseRequestedArgs ): void { args.title.owner.close(); } /** * Handle the `tabDetachRequested` signal from a tab bar. */ private _onTabDetachRequested( sender: TabBar, args: TabBar.ITabDetachRequestedArgs ): void { // Do nothing if a drag is already in progress. if (this._drag) { return; } // Release the tab bar's hold on the mouse. sender.releaseMouse(); // Extract the data from the args. let { title, tab, clientX, clientY } = args; // Setup the mime data for the drag operation. let mimeData = new MimeData(); let factory = () => title.owner; mimeData.setData('application/vnd.lumino.widget-factory', factory); // Create the drag image for the drag operation. let dragImage = tab.cloneNode(true) as HTMLElement; // Create the drag object to manage the drag-drop operation. this._drag = new Drag({ mimeData, dragImage, proposedAction: 'move', supportedActions: 'move', source: this }); // Hide the tab node in the original tab. tab.classList.add('lm-mod-hidden'); /* */ tab.classList.add('p-mod-hidden'); // Create the cleanup callback. /* */ let cleanup = () => { this._drag = null; tab.classList.remove('lm-mod-hidden'); /* */ tab.classList.remove('p-mod-hidden'); /* */ }; // Start the drag operation and cleanup when done. this._drag.start(clientX, clientY).then(cleanup); } private _edges: DockPanel.IEdges; private _mode: DockPanel.Mode; private _drag: Drag | null = null; private _renderer: DockPanel.IRenderer; private _tabsMovable: boolean = true; private _tabsConstrained: boolean = false; private _addButtonEnabled: boolean = false; private _pressData: Private.IPressData | null = null; private _layoutModified = new Signal(this); private _addRequested = new Signal>(this); } /** * The namespace for the `DockPanel` class statics. */ export namespace DockPanel { /** * An options object for creating a dock panel. */ export interface IOptions { /** * The overlay to use with the dock panel. * * The default is a new `Overlay` instance. */ overlay?: IOverlay; /** * The renderer to use for the dock panel. * * The default is a shared renderer instance. */ renderer?: IRenderer; /** * The spacing between the items in the panel. * * The default is `4`. */ spacing?: number; /** * The mode for the dock panel. * * The default is `'multiple-document'`. */ mode?: DockPanel.Mode; /** * The sizes of the edge drop zones, in pixels. * If not given, default values will be used. */ edges?: IEdges; /** * The method for hiding widgets. * * The default is `Widget.HiddenMode.Display`. */ hiddenMode?: Widget.HiddenMode; /** * Allow tabs to be draggable / movable by user. * * The default is `'true'`. */ tabsMovable?: boolean; /** * Constrain tabs to this dock panel * * The default is `'false'`. */ tabsConstrained?: boolean; /** * Enable add buttons in each of the dock panel's tab bars. * * The default is `'false'`. */ addButtonEnabled?: boolean; } /** * The sizes of the edge drop zones, in pixels. */ export interface IEdges { /** * The size of the top edge drop zone. */ top: number; /** * The size of the right edge drop zone. */ right: number; /** * The size of the bottom edge drop zone. */ bottom: number; /** * The size of the left edge drop zone. */ left: number; } /** * A type alias for the supported dock panel modes. */ export type Mode = | /** * The single document mode. * * In this mode, only a single widget is visible at a time, and that * widget fills the available layout space. No tab bars are visible. */ 'single-document' /** * The multiple document mode. * * In this mode, multiple documents are displayed in separate tab * areas, and those areas can be individually resized by the user. */ | 'multiple-document'; /** * A type alias for a layout configuration object. */ export type ILayoutConfig = DockLayout.ILayoutConfig; /** * A type alias for the supported insertion modes. */ export type InsertMode = DockLayout.InsertMode; /** * A type alias for the add widget options. */ export type IAddOptions = DockLayout.IAddOptions; /** * An object which holds the geometry for overlay positioning. */ export interface IOverlayGeometry { /** * The distance between the overlay and parent top edges. */ top: number; /** * The distance between the overlay and parent left edges. */ left: number; /** * The distance between the overlay and parent right edges. */ right: number; /** * The distance between the overlay and parent bottom edges. */ bottom: number; } /** * An object which manages the overlay node for a dock panel. */ export interface IOverlay { /** * The DOM node for the overlay. */ readonly node: HTMLDivElement; /** * Show the overlay using the given overlay geometry. * * @param geo - The desired geometry for the overlay. * * #### Notes * The given geometry values assume the node will use absolute * positioning. * * This is called on every mouse move event during a drag in order * to update the position of the overlay. It should be efficient. */ show(geo: IOverlayGeometry): void; /** * Hide the overlay node. * * @param delay - The delay (in ms) before hiding the overlay. * A delay value <= 0 should hide the overlay immediately. * * #### Notes * This is called whenever the overlay node should been hidden. */ hide(delay: number): void; } /** * A concrete implementation of `IOverlay`. * * This is the default overlay implementation for a dock panel. */ export class Overlay implements IOverlay { /** * Construct a new overlay. */ constructor() { this.node = document.createElement('div'); this.node.classList.add('lm-DockPanel-overlay'); this.node.classList.add('lm-mod-hidden'); /* */ this.node.classList.add('p-DockPanel-overlay'); this.node.classList.add('p-mod-hidden'); /* */ this.node.style.position = 'absolute'; } /** * The DOM node for the overlay. */ readonly node: HTMLDivElement; /** * Show the overlay using the given overlay geometry. * * @param geo - The desired geometry for the overlay. */ show(geo: IOverlayGeometry): void { // Update the position of the overlay. let style = this.node.style; style.top = `${geo.top}px`; style.left = `${geo.left}px`; style.right = `${geo.right}px`; style.bottom = `${geo.bottom}px`; // Clear any pending hide timer. clearTimeout(this._timer); this._timer = -1; // If the overlay is already visible, we're done. if (!this._hidden) { return; } // Clear the hidden flag. this._hidden = false; // Finally, show the overlay. this.node.classList.remove('lm-mod-hidden'); /* */ this.node.classList.remove('p-mod-hidden'); /* */ } /** * Hide the overlay node. * * @param delay - The delay (in ms) before hiding the overlay. * A delay value <= 0 will hide the overlay immediately. */ hide(delay: number): void { // Do nothing if the overlay is already hidden. if (this._hidden) { return; } // Hide immediately if the delay is <= 0. if (delay <= 0) { clearTimeout(this._timer); this._timer = -1; this._hidden = true; this.node.classList.add('lm-mod-hidden'); /* */ this.node.classList.add('p-mod-hidden'); /* */ return; } // Do nothing if a hide is already pending. if (this._timer !== -1) { return; } // Otherwise setup the hide timer. this._timer = window.setTimeout(() => { this._timer = -1; this._hidden = true; this.node.classList.add('lm-mod-hidden'); /* */ this.node.classList.add('p-mod-hidden'); /* */ }, delay); } private _timer = -1; private _hidden = true; } /** * A type alias for a dock panel renderer; */ export type IRenderer = DockLayout.IRenderer; /** * The default implementation of `IRenderer`. */ export class Renderer implements IRenderer { /** * Create a new tab bar for use with a dock panel. * * @returns A new tab bar for a dock panel. */ createTabBar(): TabBar { let bar = new TabBar(); bar.addClass('lm-DockPanel-tabBar'); /* */ bar.addClass('p-DockPanel-tabBar'); /* */ return bar; } /** * Create a new handle node for use with a dock panel. * * @returns A new handle node for a dock panel. */ createHandle(): HTMLDivElement { let handle = document.createElement('div'); handle.className = 'lm-DockPanel-handle'; /* */ handle.classList.add('p-DockPanel-handle'); /* */ return handle; } } /** * The default `Renderer` instance. */ export const defaultRenderer = new Renderer(); } /** * The namespace for the module implementation details. */ namespace Private { /** * A fraction used for sizing root panels; ~= `1 / golden_ratio`. */ export const GOLDEN_RATIO = 0.618; /** * The default sizes for the edge drop zones, in pixels. */ export const DEFAULT_EDGES = { /** * The size of the top edge dock zone for the root panel, in pixels. * This is different from the others to distinguish between the top * tab bar and the top root zone. */ top: 12, /** * The size of the edge dock zone for the root panel, in pixels. */ right: 40, /** * The size of the edge dock zone for the root panel, in pixels. */ bottom: 40, /** * The size of the edge dock zone for the root panel, in pixels. */ left: 40 }; /** * A singleton `'layout-modified'` conflatable message. */ export const LayoutModified = new ConflatableMessage('layout-modified'); /** * An object which holds mouse press data. */ export interface IPressData { /** * The handle which was pressed. */ handle: HTMLDivElement; /** * The X offset of the press in handle coordinates. */ deltaX: number; /** * The Y offset of the press in handle coordinates. */ deltaY: number; /** * The disposable which will clear the override cursor. */ override: IDisposable; } /** * A type alias for a drop zone. */ export type DropZone = | /** * An invalid drop zone. */ 'invalid' /** * The entirety of the root dock area. */ | 'root-all' /** * The top portion of the root dock area. */ | 'root-top' /** * The left portion of the root dock area. */ | 'root-left' /** * The right portion of the root dock area. */ | 'root-right' /** * The bottom portion of the root dock area. */ | 'root-bottom' /** * The entirety of a tabbed widget area. */ | 'widget-all' /** * The top portion of tabbed widget area. */ | 'widget-top' /** * The left portion of tabbed widget area. */ | 'widget-left' /** * The right portion of tabbed widget area. */ | 'widget-right' /** * The bottom portion of tabbed widget area. */ | 'widget-bottom' /** * The the bar of a tabbed widget area. */ | 'widget-tab'; /** * An object which holds the drop target zone and widget. */ export interface IDropTarget { /** * The semantic zone for the mouse position. */ zone: DropZone; /** * The tab area geometry for the drop zone, or `null`. */ target: DockLayout.ITabAreaGeometry | null; } /** * An attached property used to track generated tab bars. */ export const isGeneratedTabBarProperty = new AttachedProperty< Widget, boolean >({ name: 'isGeneratedTabBar', create: () => false }); /** * Create a single document config for the widgets in a dock panel. */ export function createSingleDocumentConfig( panel: DockPanel ): DockPanel.ILayoutConfig { // Return an empty config if the panel is empty. if (panel.isEmpty) { return { main: null }; } // Get a flat array of the widgets in the panel. let widgets = toArray(panel.widgets()); // Get the first selected widget in the panel. let selected = panel.selectedWidgets().next(); // Compute the current index for the new config. let currentIndex = selected ? widgets.indexOf(selected) : -1; // Return the single document config. return { main: { type: 'tab-area', widgets, currentIndex } }; } /** * Find the drop target at the given client position. */ export function findDropTarget( panel: DockPanel, clientX: number, clientY: number, edges: DockPanel.IEdges ): IDropTarget { // Bail if the mouse is not over the dock panel. if (!ElementExt.hitTest(panel.node, clientX, clientY)) { return { zone: 'invalid', target: null }; } // Look up the layout for the panel. let layout = panel.layout as DockLayout; // If the layout is empty, indicate the entire root drop zone. if (layout.isEmpty) { return { zone: 'root-all', target: null }; } // Test the edge zones when in multiple document mode. if (panel.mode === 'multiple-document') { // Get the client rect for the dock panel. let panelRect = panel.node.getBoundingClientRect(); // Compute the distance to each edge of the panel. let pl = clientX - panelRect.left + 1; let pt = clientY - panelRect.top + 1; let pr = panelRect.right - clientX; let pb = panelRect.bottom - clientY; // Find the minimum distance to an edge. let pd = Math.min(pt, pr, pb, pl); // Return a root zone if the mouse is within an edge. switch (pd) { case pt: if (pt < edges.top) { return { zone: 'root-top', target: null }; } break; case pr: if (pr < edges.right) { return { zone: 'root-right', target: null }; } break; case pb: if (pb < edges.bottom) { return { zone: 'root-bottom', target: null }; } break; case pl: if (pl < edges.left) { return { zone: 'root-left', target: null }; } break; default: throw 'unreachable'; } } // Hit test the dock layout at the given client position. let target = layout.hitTestTabAreas(clientX, clientY); // Bail if no target area was found. if (!target) { return { zone: 'invalid', target: null }; } // Return the whole tab area when in single document mode. if (panel.mode === 'single-document') { return { zone: 'widget-all', target }; } // Compute the distance to each edge of the tab area. let al = target.x - target.left + 1; let at = target.y - target.top + 1; let ar = target.left + target.width - target.x; let ab = target.top + target.height - target.y; const tabHeight = target.tabBar.node.getBoundingClientRect().height; if (at < tabHeight) { return { zone: 'widget-tab', target }; } // Get the X and Y edge sizes for the area. let rx = Math.round(target.width / 3); let ry = Math.round(target.height / 3); // If the mouse is not within an edge, indicate the entire area. if (al > rx && ar > rx && at > ry && ab > ry) { return { zone: 'widget-all', target }; } // Scale the distances by the slenderness ratio. al /= rx; at /= ry; ar /= rx; ab /= ry; // Find the minimum distance to the area edge. let ad = Math.min(al, at, ar, ab); // Find the widget zone for the area edge. let zone: DropZone; switch (ad) { case al: zone = 'widget-left'; break; case at: zone = 'widget-top'; break; case ar: zone = 'widget-right'; break; case ab: zone = 'widget-bottom'; break; default: throw 'unreachable'; } // Return the final drop target. return { zone, target }; } /** * Get the drop reference widget for a tab bar. */ export function getDropRef(tabBar: TabBar): Widget | null { if (tabBar.titles.length === 0) { return null; } if (tabBar.currentTitle) { return tabBar.currentTitle.owner; } return tabBar.titles[tabBar.titles.length - 1].owner; } } lumino-2021.12.13/packages/widgets/src/focustracker.ts000066400000000000000000000250301415564225700224770ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { ArrayExt, each, filter, find, max } from '@lumino/algorithm'; import { IDisposable } from '@lumino/disposable'; import { ISignal, Signal } from '@lumino/signaling'; import { Widget } from './widget'; /** * A class which tracks focus among a set of widgets. * * This class is useful when code needs to keep track of the most * recently focused widget(s) among a set of related widgets. */ export class FocusTracker implements IDisposable { /** * Dispose of the resources held by the tracker. */ dispose(): void { // Do nothing if the tracker is already disposed. if (this._counter < 0) { return; } // Mark the tracker as disposed. this._counter = -1; // Clear the connections for the tracker. Signal.clearData(this); // Remove all event listeners. each(this._widgets, w => { w.node.removeEventListener('focus', this, true); w.node.removeEventListener('blur', this, true); }); // Clear the internal data structures. this._activeWidget = null; this._currentWidget = null; this._nodes.clear(); this._numbers.clear(); this._widgets.length = 0; } /** * A signal emitted when the current widget has changed. */ get currentChanged(): ISignal> { return this._currentChanged; } /** * A signal emitted when the active widget has changed. */ get activeChanged(): ISignal> { return this._activeChanged; } /** * A flag indicating whether the tracker is disposed. */ get isDisposed(): boolean { return this._counter < 0; } /** * The current widget in the tracker. * * #### Notes * The current widget is the widget among the tracked widgets which * has the *descendant node* which has most recently been focused. * * The current widget will not be updated if the node loses focus. It * will only be updated when a different tracked widget gains focus. * * If the current widget is removed from the tracker, the previous * current widget will be restored. * * This behavior is intended to follow a user's conceptual model of * a semantically "current" widget, where the "last thing of type X" * to be interacted with is the "current instance of X", regardless * of whether that instance still has focus. */ get currentWidget(): T | null { return this._currentWidget; } /** * The active widget in the tracker. * * #### Notes * The active widget is the widget among the tracked widgets which * has the *descendant node* which is currently focused. */ get activeWidget(): T | null { return this._activeWidget; } /** * A read only array of the widgets being tracked. */ get widgets(): ReadonlyArray { return this._widgets; } /** * Get the focus number for a particular widget in the tracker. * * @param widget - The widget of interest. * * @returns The focus number for the given widget, or `-1` if the * widget has not had focus since being added to the tracker, or * is not contained by the tracker. * * #### Notes * The focus number indicates the relative order in which the widgets * have gained focus. A widget with a larger number has gained focus * more recently than a widget with a smaller number. * * The `currentWidget` will always have the largest focus number. * * All widgets start with a focus number of `-1`, which indicates that * the widget has not been focused since being added to the tracker. */ focusNumber(widget: T): number { let n = this._numbers.get(widget); return n === undefined ? -1 : n; } /** * Test whether the focus tracker contains a given widget. * * @param widget - The widget of interest. * * @returns `true` if the widget is tracked, `false` otherwise. */ has(widget: T): boolean { return this._numbers.has(widget); } /** * Add a widget to the focus tracker. * * @param widget - The widget of interest. * * #### Notes * A widget will be automatically removed from the tracker if it * is disposed after being added. * * If the widget is already tracked, this is a no-op. */ add(widget: T): void { // Do nothing if the widget is already tracked. if (this._numbers.has(widget)) { return; } // Test whether the widget has focus. let focused = widget.node.contains(document.activeElement); // Set up the initial focus number. let n = focused ? this._counter++ : -1; // Add the widget to the internal data structures. this._widgets.push(widget); this._numbers.set(widget, n); this._nodes.set(widget.node, widget); // Set up the event listeners. The capturing phase must be used // since the 'focus' and 'blur' events don't bubble and Firefox // doesn't support the 'focusin' or 'focusout' events. widget.node.addEventListener('focus', this, true); widget.node.addEventListener('blur', this, true); // Connect the disposed signal handler. widget.disposed.connect(this._onWidgetDisposed, this); // Set the current and active widgets if needed. if (focused) { this._setWidgets(widget, widget); } } /** * Remove a widget from the focus tracker. * * #### Notes * If the widget is the `currentWidget`, the previous current widget * will become the new `currentWidget`. * * A widget will be automatically removed from the tracker if it * is disposed after being added. * * If the widget is not tracked, this is a no-op. */ remove(widget: T): void { // Bail early if the widget is not tracked. if (!this._numbers.has(widget)) { return; } // Disconnect the disposed signal handler. widget.disposed.disconnect(this._onWidgetDisposed, this); // Remove the event listeners. widget.node.removeEventListener('focus', this, true); widget.node.removeEventListener('blur', this, true); // Remove the widget from the internal data structures. ArrayExt.removeFirstOf(this._widgets, widget); this._nodes.delete(widget.node); this._numbers.delete(widget); // Bail early if the widget is not the current widget. if (this._currentWidget !== widget) { return; } // Filter the widgets for those which have had focus. let valid = filter(this._widgets, w => this._numbers.get(w) !== -1); // Get the valid widget with the max focus number. let previous = max(valid, (first, second) => { let a = this._numbers.get(first)!; let b = this._numbers.get(second)!; return a - b; }) || null; // Set the current and active widgets. this._setWidgets(previous, null); } /** * Handle the DOM events for the focus tracker. * * @param event - The DOM event sent to the panel. * * #### Notes * This method implements the DOM `EventListener` interface and is * called in response to events on the tracked nodes. It should * not be called directly by user code. */ handleEvent(event: Event): void { switch (event.type) { case 'focus': this._evtFocus(event as FocusEvent); break; case 'blur': this._evtBlur(event as FocusEvent); break; } } /** * Set the current and active widgets for the tracker. */ private _setWidgets(current: T | null, active: T | null): void { // Swap the current widget. let oldCurrent = this._currentWidget; this._currentWidget = current; // Swap the active widget. let oldActive = this._activeWidget; this._activeWidget = active; // Emit the `currentChanged` signal if needed. if (oldCurrent !== current) { this._currentChanged.emit({ oldValue: oldCurrent, newValue: current }); } // Emit the `activeChanged` signal if needed. if (oldActive !== active) { this._activeChanged.emit({ oldValue: oldActive, newValue: active }); } } /** * Handle the `'focus'` event for a tracked widget. */ private _evtFocus(event: FocusEvent): void { // Find the widget which gained focus, which is known to exist. let widget = this._nodes.get(event.currentTarget as HTMLElement)!; // Update the focus number if necessary. if (widget !== this._currentWidget) { this._numbers.set(widget, this._counter++); } // Set the current and active widgets. this._setWidgets(widget, widget); } /** * Handle the `'blur'` event for a tracked widget. */ private _evtBlur(event: FocusEvent): void { // Find the widget which lost focus, which is known to exist. let widget = this._nodes.get(event.currentTarget as HTMLElement)!; // Get the node which being focused after this blur. let focusTarget = event.relatedTarget as HTMLElement; // If no other node is being focused, clear the active widget. if (!focusTarget) { this._setWidgets(this._currentWidget, null); return; } // Bail if the focus widget is not changing. if (widget.node.contains(focusTarget)) { return; } // If no tracked widget is being focused, clear the active widget. if (!find(this._widgets, w => w.node.contains(focusTarget))) { this._setWidgets(this._currentWidget, null); return; } } /** * Handle the `disposed` signal for a tracked widget. */ private _onWidgetDisposed(sender: T): void { this.remove(sender); } private _counter = 0; private _widgets: T[] = []; private _activeWidget: T | null = null; private _currentWidget: T | null = null; private _numbers = new Map(); private _nodes = new Map(); private _activeChanged = new Signal>(this); private _currentChanged = new Signal>( this ); } /** * The namespace for the `FocusTracker` class statics. */ export namespace FocusTracker { /** * An arguments object for the changed signals. */ export interface IChangedArgs { /** * The old value for the widget. */ oldValue: T | null; /** * The new value for the widget. */ newValue: T | null; } } lumino-2021.12.13/packages/widgets/src/gridlayout.ts000066400000000000000000000545331415564225700222010ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { ArrayExt, each, IIterator, map } from '@lumino/algorithm'; import { ElementExt } from '@lumino/domutils'; import { Message, MessageLoop } from '@lumino/messaging'; import { AttachedProperty } from '@lumino/properties'; import { BoxEngine, BoxSizer } from './boxengine'; import { Layout, LayoutItem } from './layout'; import { Widget } from './widget'; /** * A layout which arranges its widgets in a grid. */ export class GridLayout extends Layout { /** * Construct a new grid layout. * * @param options - The options for initializing the layout. */ constructor(options: GridLayout.IOptions = {}) { super(options); if (options.rowCount !== undefined) { Private.reallocSizers(this._rowSizers, options.rowCount); } if (options.columnCount !== undefined) { Private.reallocSizers(this._columnSizers, options.columnCount); } if (options.rowSpacing !== undefined) { this._rowSpacing = Private.clampValue(options.rowSpacing); } if (options.columnSpacing !== undefined) { this._columnSpacing = Private.clampValue(options.columnSpacing); } } /** * Dispose of the resources held by the layout. */ dispose(): void { // Dispose of the widgets and layout items. each(this._items, item => { let widget = item.widget; item.dispose(); widget.dispose(); }); // Clear the layout state. this._box = null; this._items.length = 0; this._rowStarts.length = 0; this._rowSizers.length = 0; this._columnStarts.length = 0; this._columnSizers.length = 0; // Dispose of the rest of the layout. super.dispose(); } /** * Get the number of rows in the layout. */ get rowCount(): number { return this._rowSizers.length; } /** * Set the number of rows in the layout. * * #### Notes * The minimum row count is `1`. */ set rowCount(value: number) { // Do nothing if the row count does not change. if (value === this.rowCount) { return; } // Reallocate the row sizers. Private.reallocSizers(this._rowSizers, value); // Schedule a fit of the parent. if (this.parent) { this.parent.fit(); } } /** * Get the number of columns in the layout. */ get columnCount(): number { return this._columnSizers.length; } /** * Set the number of columns in the layout. * * #### Notes * The minimum column count is `1`. */ set columnCount(value: number) { // Do nothing if the column count does not change. if (value === this.columnCount) { return; } // Reallocate the column sizers. Private.reallocSizers(this._columnSizers, value); // Schedule a fit of the parent. if (this.parent) { this.parent.fit(); } } /** * Get the row spacing for the layout. */ get rowSpacing(): number { return this._rowSpacing; } /** * Set the row spacing for the layout. */ set rowSpacing(value: number) { // Clamp the spacing to the allowed range. value = Private.clampValue(value); // Bail if the spacing does not change if (this._rowSpacing === value) { return; } // Update the internal spacing. this._rowSpacing = value; // Schedule a fit of the parent. if (this.parent) { this.parent.fit(); } } /** * Get the column spacing for the layout. */ get columnSpacing(): number { return this._columnSpacing; } /** * Set the col spacing for the layout. */ set columnSpacing(value: number) { // Clamp the spacing to the allowed range. value = Private.clampValue(value); // Bail if the spacing does not change if (this._columnSpacing === value) { return; } // Update the internal spacing. this._columnSpacing = value; // Schedule a fit of the parent. if (this.parent) { this.parent.fit(); } } /** * Get the stretch factor for a specific row. * * @param index - The row index of interest. * * @returns The stretch factor for the row. * * #### Notes * This returns `-1` if the index is out of range. */ rowStretch(index: number): number { let sizer = this._rowSizers[index]; return sizer ? sizer.stretch : -1; } /** * Set the stretch factor for a specific row. * * @param index - The row index of interest. * * @param value - The stretch factor for the row. * * #### Notes * This is a no-op if the index is out of range. */ setRowStretch(index: number, value: number): void { // Look up the row sizer. let sizer = this._rowSizers[index]; // Bail if the index is out of range. if (!sizer) { return; } // Clamp the value to the allowed range. value = Private.clampValue(value); // Bail if the stretch does not change. if (sizer.stretch === value) { return; } // Update the sizer stretch. sizer.stretch = value; // Schedule an update of the parent. if (this.parent) { this.parent.update(); } } /** * Get the stretch factor for a specific column. * * @param index - The column index of interest. * * @returns The stretch factor for the column. * * #### Notes * This returns `-1` if the index is out of range. */ columnStretch(index: number): number { let sizer = this._columnSizers[index]; return sizer ? sizer.stretch : -1; } /** * Set the stretch factor for a specific column. * * @param index - The column index of interest. * * @param value - The stretch factor for the column. * * #### Notes * This is a no-op if the index is out of range. */ setColumnStretch(index: number, value: number): void { // Look up the column sizer. let sizer = this._columnSizers[index]; // Bail if the index is out of range. if (!sizer) { return; } // Clamp the value to the allowed range. value = Private.clampValue(value); // Bail if the stretch does not change. if (sizer.stretch === value) { return; } // Update the sizer stretch. sizer.stretch = value; // Schedule an update of the parent. if (this.parent) { this.parent.update(); } } /** * Create an iterator over the widgets in the layout. * * @returns A new iterator over the widgets in the layout. */ iter(): IIterator { return map(this._items, item => item.widget); } /** * Add a widget to the grid layout. * * @param widget - The widget to add to the layout. * * #### Notes * If the widget is already contained in the layout, this is no-op. */ addWidget(widget: Widget): void { // Look up the index for the widget. let i = ArrayExt.findFirstIndex(this._items, it => it.widget === widget); // Bail if the widget is already in the layout. if (i !== -1) { return; } // Add the widget to the layout. this._items.push(new LayoutItem(widget)); // Attach the widget to the parent. if (this.parent) { this.attachWidget(widget); } } /** * Remove a widget from the grid layout. * * @param widget - The widget to remove from the layout. * * #### Notes * A widget is automatically removed from the layout when its `parent` * is set to `null`. This method should only be invoked directly when * removing a widget from a layout which has yet to be installed on a * parent widget. * * This method does *not* modify the widget's `parent`. */ removeWidget(widget: Widget): void { // Look up the index for the widget. let i = ArrayExt.findFirstIndex(this._items, it => it.widget === widget); // Bail if the widget is not in the layout. if (i === -1) { return; } // Remove the widget from the layout. let item = ArrayExt.removeAt(this._items, i)!; // Detach the widget from the parent. if (this.parent) { this.detachWidget(widget); } // Dispose the layout item. item.dispose(); } /** * Perform layout initialization which requires the parent widget. */ protected init(): void { super.init(); each(this, widget => { this.attachWidget(widget); }); } /** * Attach a widget to the parent's DOM node. * * @param widget - The widget to attach to the parent. */ protected attachWidget(widget: Widget): void { // Send a `'before-attach'` message if the parent is attached. if (this.parent!.isAttached) { MessageLoop.sendMessage(widget, Widget.Msg.BeforeAttach); } // Add the widget's node to the parent. this.parent!.node.appendChild(widget.node); // Send an `'after-attach'` message if the parent is attached. if (this.parent!.isAttached) { MessageLoop.sendMessage(widget, Widget.Msg.AfterAttach); } // Post a fit request for the parent widget. this.parent!.fit(); } /** * Detach a widget from the parent's DOM node. * * @param widget - The widget to detach from the parent. */ protected detachWidget(widget: Widget): void { // Send a `'before-detach'` message if the parent is attached. if (this.parent!.isAttached) { MessageLoop.sendMessage(widget, Widget.Msg.BeforeDetach); } // Remove the widget's node from the parent. this.parent!.node.removeChild(widget.node); // Send an `'after-detach'` message if the parent is attached. if (this.parent!.isAttached) { MessageLoop.sendMessage(widget, Widget.Msg.AfterDetach); } // Post a fit request for the parent widget. this.parent!.fit(); } /** * A message handler invoked on a `'before-show'` message. */ protected onBeforeShow(msg: Message): void { super.onBeforeShow(msg); this.parent!.update(); } /** * A message handler invoked on a `'before-attach'` message. */ protected onBeforeAttach(msg: Message): void { super.onBeforeAttach(msg); this.parent!.fit(); } /** * A message handler invoked on a `'child-shown'` message. */ protected onChildShown(msg: Widget.ChildMessage): void { this.parent!.fit(); } /** * A message handler invoked on a `'child-hidden'` message. */ protected onChildHidden(msg: Widget.ChildMessage): void { this.parent!.fit(); } /** * A message handler invoked on a `'resize'` message. */ protected onResize(msg: Widget.ResizeMessage): void { if (this.parent!.isVisible) { this._update(msg.width, msg.height); } } /** * A message handler invoked on an `'update-request'` message. */ protected onUpdateRequest(msg: Message): void { if (this.parent!.isVisible) { this._update(-1, -1); } } /** * A message handler invoked on a `'fit-request'` message. */ protected onFitRequest(msg: Message): void { if (this.parent!.isAttached) { this._fit(); } } /** * Fit the layout to the total size required by the widgets. */ private _fit(): void { // Reset the min sizes of the sizers. for (let i = 0, n = this.rowCount; i < n; ++i) { this._rowSizers[i].minSize = 0; } for (let i = 0, n = this.columnCount; i < n; ++i) { this._columnSizers[i].minSize = 0; } // Filter for the visible layout items. let items = this._items.filter(it => !it.isHidden); // Fit the layout items. for (let i = 0, n = items.length; i < n; ++i) { items[i].fit(); } // Get the max row and column index. let maxRow = this.rowCount - 1; let maxCol = this.columnCount - 1; // Sort the items by row span. items.sort(Private.rowSpanCmp); // Update the min sizes of the row sizers. for (let i = 0, n = items.length; i < n; ++i) { // Fetch the item. let item = items[i]; // Get the row bounds for the item. let config = GridLayout.getCellConfig(item.widget); let r1 = Math.min(config.row, maxRow); let r2 = Math.min(config.row + config.rowSpan - 1, maxRow); // Distribute the minimum height to the sizers as needed. Private.distributeMin(this._rowSizers, r1, r2, item.minHeight); } // Sort the items by column span. items.sort(Private.columnSpanCmp); // Update the min sizes of the column sizers. for (let i = 0, n = items.length; i < n; ++i) { // Fetch the item. let item = items[i]; // Get the column bounds for the item. let config = GridLayout.getCellConfig(item.widget); let c1 = Math.min(config.column, maxCol); let c2 = Math.min(config.column + config.columnSpan - 1, maxCol); // Distribute the minimum width to the sizers as needed. Private.distributeMin(this._columnSizers, c1, c2, item.minWidth); } // If no size constraint is needed, just update the parent. if (this.fitPolicy === 'set-no-constraint') { MessageLoop.sendMessage(this.parent!, Widget.Msg.UpdateRequest); return; } // Set up the computed min size. let minH = maxRow * this._rowSpacing; let minW = maxCol * this._columnSpacing; // Add the sizer minimums to the computed min size. for (let i = 0, n = this.rowCount; i < n; ++i) { minH += this._rowSizers[i].minSize; } for (let i = 0, n = this.columnCount; i < n; ++i) { minW += this._columnSizers[i].minSize; } // Update the box sizing and add it to the computed min size. let box = (this._box = ElementExt.boxSizing(this.parent!.node)); minW += box.horizontalSum; minH += box.verticalSum; // Update the parent's min size constraints. let style = this.parent!.node.style; style.minWidth = `${minW}px`; style.minHeight = `${minH}px`; // Set the dirty flag to ensure only a single update occurs. this._dirty = true; // Notify the ancestor that it should fit immediately. This may // cause a resize of the parent, fulfilling the required update. if (this.parent!.parent) { MessageLoop.sendMessage(this.parent!.parent!, Widget.Msg.FitRequest); } // If the dirty flag is still set, the parent was not resized. // Trigger the required update on the parent widget immediately. if (this._dirty) { MessageLoop.sendMessage(this.parent!, Widget.Msg.UpdateRequest); } } /** * Update the layout position and size of the widgets. * * The parent offset dimensions should be `-1` if unknown. */ private _update(offsetWidth: number, offsetHeight: number): void { // Clear the dirty flag to indicate the update occurred. this._dirty = false; // Measure the parent if the offset dimensions are unknown. if (offsetWidth < 0) { offsetWidth = this.parent!.node.offsetWidth; } if (offsetHeight < 0) { offsetHeight = this.parent!.node.offsetHeight; } // Ensure the parent box sizing data is computed. if (!this._box) { this._box = ElementExt.boxSizing(this.parent!.node); } // Compute the layout area adjusted for border and padding. let top = this._box.paddingTop; let left = this._box.paddingLeft; let width = offsetWidth - this._box.horizontalSum; let height = offsetHeight - this._box.verticalSum; // Get the max row and column index. let maxRow = this.rowCount - 1; let maxCol = this.columnCount - 1; // Compute the total fixed row and column space. let fixedRowSpace = maxRow * this._rowSpacing; let fixedColSpace = maxCol * this._columnSpacing; // Distribute the available space to the box sizers. BoxEngine.calc(this._rowSizers, Math.max(0, height - fixedRowSpace)); BoxEngine.calc(this._columnSizers, Math.max(0, width - fixedColSpace)); // Update the row start positions. for (let i = 0, pos = top, n = this.rowCount; i < n; ++i) { this._rowStarts[i] = pos; pos += this._rowSizers[i].size + this._rowSpacing; } // Update the column start positions. for (let i = 0, pos = left, n = this.columnCount; i < n; ++i) { this._columnStarts[i] = pos; pos += this._columnSizers[i].size + this._columnSpacing; } // Update the geometry of the layout items. for (let i = 0, n = this._items.length; i < n; ++i) { // Fetch the item. let item = this._items[i]; // Ignore hidden items. if (item.isHidden) { continue; } // Fetch the cell bounds for the widget. let config = GridLayout.getCellConfig(item.widget); let r1 = Math.min(config.row, maxRow); let c1 = Math.min(config.column, maxCol); let r2 = Math.min(config.row + config.rowSpan - 1, maxRow); let c2 = Math.min(config.column + config.columnSpan - 1, maxCol); // Compute the cell geometry. let x = this._columnStarts[c1]; let y = this._rowStarts[r1]; let w = this._columnStarts[c2] + this._columnSizers[c2].size - x; let h = this._rowStarts[r2] + this._rowSizers[r2].size - y; // Update the geometry of the layout item. item.update(x, y, w, h); } } private _dirty = false; private _rowSpacing = 4; private _columnSpacing = 4; private _items: LayoutItem[] = []; private _rowStarts: number[] = []; private _columnStarts: number[] = []; private _rowSizers: BoxSizer[] = [new BoxSizer()]; private _columnSizers: BoxSizer[] = [new BoxSizer()]; private _box: ElementExt.IBoxSizing | null = null; } /** * The namespace for the `GridLayout` class statics. */ export namespace GridLayout { /** * An options object for initializing a grid layout. */ export interface IOptions extends Layout.IOptions { /** * The initial row count for the layout. * * The default is `1`. */ rowCount?: number; /** * The initial column count for the layout. * * The default is `1`. */ columnCount?: number; /** * The spacing between rows in the layout. * * The default is `4`. */ rowSpacing?: number; /** * The spacing between columns in the layout. * * The default is `4`. */ columnSpacing?: number; } /** * An object which holds the cell configuration for a widget. */ export interface ICellConfig { /** * The row index for the widget. */ readonly row: number; /** * The column index for the widget. */ readonly column: number; /** * The row span for the widget. */ readonly rowSpan: number; /** * The column span for the widget. */ readonly columnSpan: number; } /** * Get the cell config for the given widget. * * @param widget - The widget of interest. * * @returns The cell config for the widget. */ export function getCellConfig(widget: Widget): ICellConfig { return Private.cellConfigProperty.get(widget); } /** * Set the cell config for the given widget. * * @param widget - The widget of interest. * * @param value - The value for the cell config. */ export function setCellConfig( widget: Widget, value: Partial ): void { Private.cellConfigProperty.set(widget, Private.normalizeConfig(value)); } } /** * The namespace for the module implementation details. */ namespace Private { /** * The property descriptor for the widget cell config. */ export const cellConfigProperty = new AttachedProperty< Widget, GridLayout.ICellConfig >({ name: 'cellConfig', create: () => ({ row: 0, column: 0, rowSpan: 1, columnSpan: 1 }), changed: onChildCellConfigChanged }); /** * Normalize a partial cell config object. */ export function normalizeConfig( config: Partial ): GridLayout.ICellConfig { let row = Math.max(0, Math.floor(config.row || 0)); let column = Math.max(0, Math.floor(config.column || 0)); let rowSpan = Math.max(1, Math.floor(config.rowSpan || 0)); let columnSpan = Math.max(1, Math.floor(config.columnSpan || 0)); return { row, column, rowSpan, columnSpan }; } /** * Clamp a value to an integer >= 0. */ export function clampValue(value: number): number { return Math.max(0, Math.floor(value)); } /** * A sort comparison function for row spans. */ export function rowSpanCmp(a: LayoutItem, b: LayoutItem): number { let c1 = cellConfigProperty.get(a.widget); let c2 = cellConfigProperty.get(b.widget); return c1.rowSpan - c2.rowSpan; } /** * A sort comparison function for column spans. */ export function columnSpanCmp(a: LayoutItem, b: LayoutItem): number { let c1 = cellConfigProperty.get(a.widget); let c2 = cellConfigProperty.get(b.widget); return c1.columnSpan - c2.columnSpan; } /** * Reallocate the box sizers for the given grid dimensions. */ export function reallocSizers(sizers: BoxSizer[], count: number): void { // Coerce the count to the valid range. count = Math.max(1, Math.floor(count)); // Add the missing sizers. while (sizers.length < count) { sizers.push(new BoxSizer()); } // Remove the extra sizers. if (sizers.length > count) { sizers.length = count; } } /** * Distribute a min size constraint across a range of sizers. */ export function distributeMin( sizers: BoxSizer[], i1: number, i2: number, minSize: number ): void { // Sanity check the indices. if (i2 < i1) { return; } // Handle the simple case of no cell span. if (i1 === i2) { let sizer = sizers[i1]; sizer.minSize = Math.max(sizer.minSize, minSize); return; } // Compute the total current min size of the span. let totalMin = 0; for (let i = i1; i <= i2; ++i) { totalMin += sizers[i].minSize; } // Do nothing if the total is greater than the required. if (totalMin >= minSize) { return; } // Compute the portion of the space to allocate to each sizer. let portion = (minSize - totalMin) / (i2 - i1 + 1); // Add the portion to each sizer. for (let i = i1; i <= i2; ++i) { sizers[i].minSize += portion; } } /** * The change handler for the child cell config property. */ function onChildCellConfigChanged(child: Widget): void { if (child.parent && child.parent.layout instanceof GridLayout) { child.parent.fit(); } } } lumino-2021.12.13/packages/widgets/src/index.ts000066400000000000000000000023051415564225700211130ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ export * from './accordionlayout'; export * from './accordionpanel'; export * from './boxengine'; export * from './boxlayout'; export * from './boxpanel'; export * from './commandpalette'; export * from './contextmenu'; export * from './docklayout'; export * from './dockpanel'; export * from './focustracker'; export * from './gridlayout'; export * from './layout'; export * from './menu'; export * from './menubar'; export * from './panel'; export * from './panellayout'; export * from './scrollbar'; export * from './singletonlayout'; export * from './splitlayout'; export * from './splitpanel'; export * from './stackedlayout'; export * from './stackedpanel'; export * from './tabbar'; export * from './tabpanel'; export * from './title'; export * from './widget'; lumino-2021.12.13/packages/widgets/src/layout.ts000066400000000000000000000555141415564225700213330ustar00rootroot00000000000000/* eslint-disable @typescript-eslint/no-empty-function */ // Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { each, IIterable, IIterator } from '@lumino/algorithm'; import { IDisposable } from '@lumino/disposable'; import { ElementExt } from '@lumino/domutils'; import { Message, MessageLoop } from '@lumino/messaging'; import { AttachedProperty } from '@lumino/properties'; import { Signal } from '@lumino/signaling'; import { Widget } from './widget'; /** * An abstract base class for creating lumino layouts. * * #### Notes * A layout is used to add widgets to a parent and to arrange those * widgets within the parent's DOM node. * * This class implements the base functionality which is required of * nearly all layouts. It must be subclassed in order to be useful. * * Notably, this class does not define a uniform interface for adding * widgets to the layout. A subclass should define that API in a way * which is meaningful for its intended use. */ export abstract class Layout implements IIterable, IDisposable { /** * Construct a new layout. * * @param options - The options for initializing the layout. */ constructor(options: Layout.IOptions = {}) { this._fitPolicy = options.fitPolicy || 'set-min-size'; } /** * Dispose of the resources held by the layout. * * #### Notes * This should be reimplemented to clear and dispose of the widgets. * * All reimplementations should call the superclass method. * * This method is called automatically when the parent is disposed. */ dispose(): void { this._parent = null; this._disposed = true; Signal.clearData(this); AttachedProperty.clearData(this); } /** * Test whether the layout is disposed. */ get isDisposed(): boolean { return this._disposed; } /** * Get the parent widget of the layout. */ get parent(): Widget | null { return this._parent; } /** * Set the parent widget of the layout. * * #### Notes * This is set automatically when installing the layout on the parent * widget. The parent widget should not be set directly by user code. */ set parent(value: Widget | null) { if (this._parent === value) { return; } if (this._parent) { throw new Error('Cannot change parent widget.'); } if (value!.layout !== this) { throw new Error('Invalid parent widget.'); } this._parent = value; this.init(); } /** * Get the fit policy for the layout. * * #### Notes * The fit policy controls the computed size constraints which are * applied to the parent widget by the layout. * * Some layout implementations may ignore the fit policy. */ get fitPolicy(): Layout.FitPolicy { return this._fitPolicy; } /** * Set the fit policy for the layout. * * #### Notes * The fit policy controls the computed size constraints which are * applied to the parent widget by the layout. * * Some layout implementations may ignore the fit policy. * * Changing the fit policy will clear the current size constraint * for the parent widget and then re-fit the parent. */ set fitPolicy(value: Layout.FitPolicy) { // Bail if the policy does not change if (this._fitPolicy === value) { return; } // Update the internal policy. this._fitPolicy = value; // Clear the size constraints and schedule a fit of the parent. if (this._parent) { let style = this._parent.node.style; style.minWidth = ''; style.minHeight = ''; style.maxWidth = ''; style.maxHeight = ''; this._parent.fit(); } } /** * Create an iterator over the widgets in the layout. * * @returns A new iterator over the widgets in the layout. * * #### Notes * This abstract method must be implemented by a subclass. */ abstract iter(): IIterator; /** * Remove a widget from the layout. * * @param widget - The widget to remove from the layout. * * #### Notes * A widget is automatically removed from the layout when its `parent` * is set to `null`. This method should only be invoked directly when * removing a widget from a layout which has yet to be installed on a * parent widget. * * This method should *not* modify the widget's `parent`. */ abstract removeWidget(widget: Widget): void; /** * Process a message sent to the parent widget. * * @param msg - The message sent to the parent widget. * * #### Notes * This method is called by the parent widget to process a message. * * Subclasses may reimplement this method as needed. */ processParentMessage(msg: Message): void { switch (msg.type) { case 'resize': this.onResize(msg as Widget.ResizeMessage); break; case 'update-request': this.onUpdateRequest(msg); break; case 'fit-request': this.onFitRequest(msg); break; case 'before-show': this.onBeforeShow(msg); break; case 'after-show': this.onAfterShow(msg); break; case 'before-hide': this.onBeforeHide(msg); break; case 'after-hide': this.onAfterHide(msg); break; case 'before-attach': this.onBeforeAttach(msg); break; case 'after-attach': this.onAfterAttach(msg); break; case 'before-detach': this.onBeforeDetach(msg); break; case 'after-detach': this.onAfterDetach(msg); break; case 'child-removed': this.onChildRemoved(msg as Widget.ChildMessage); break; case 'child-shown': this.onChildShown(msg as Widget.ChildMessage); break; case 'child-hidden': this.onChildHidden(msg as Widget.ChildMessage); break; } } /** * Perform layout initialization which requires the parent widget. * * #### Notes * This method is invoked immediately after the layout is installed * on the parent widget. * * The default implementation reparents all of the widgets to the * layout parent widget. * * Subclasses should reimplement this method and attach the child * widget nodes to the parent widget's node. */ protected init(): void { each(this, widget => { widget.parent = this.parent; }); } /** * A message handler invoked on a `'resize'` message. * * #### Notes * The layout should ensure that its widgets are resized according * to the specified layout space, and that they are sent a `'resize'` * message if appropriate. * * The default implementation of this method sends an `UnknownSize` * resize message to all widgets. * * This may be reimplemented by subclasses as needed. */ protected onResize(msg: Widget.ResizeMessage): void { each(this, widget => { MessageLoop.sendMessage(widget, Widget.ResizeMessage.UnknownSize); }); } /** * A message handler invoked on an `'update-request'` message. * * #### Notes * The layout should ensure that its widgets are resized according * to the available layout space, and that they are sent a `'resize'` * message if appropriate. * * The default implementation of this method sends an `UnknownSize` * resize message to all widgets. * * This may be reimplemented by subclasses as needed. */ protected onUpdateRequest(msg: Message): void { each(this, widget => { MessageLoop.sendMessage(widget, Widget.ResizeMessage.UnknownSize); }); } /** * A message handler invoked on a `'before-attach'` message. * * #### Notes * The default implementation of this method forwards the message * to all widgets. It assumes all widget nodes are attached to the * parent widget node. * * This may be reimplemented by subclasses as needed. */ protected onBeforeAttach(msg: Message): void { each(this, widget => { MessageLoop.sendMessage(widget, msg); }); } /** * A message handler invoked on an `'after-attach'` message. * * #### Notes * The default implementation of this method forwards the message * to all widgets. It assumes all widget nodes are attached to the * parent widget node. * * This may be reimplemented by subclasses as needed. */ protected onAfterAttach(msg: Message): void { each(this, widget => { MessageLoop.sendMessage(widget, msg); }); } /** * A message handler invoked on a `'before-detach'` message. * * #### Notes * The default implementation of this method forwards the message * to all widgets. It assumes all widget nodes are attached to the * parent widget node. * * This may be reimplemented by subclasses as needed. */ protected onBeforeDetach(msg: Message): void { each(this, widget => { MessageLoop.sendMessage(widget, msg); }); } /** * A message handler invoked on an `'after-detach'` message. * * #### Notes * The default implementation of this method forwards the message * to all widgets. It assumes all widget nodes are attached to the * parent widget node. * * This may be reimplemented by subclasses as needed. */ protected onAfterDetach(msg: Message): void { each(this, widget => { MessageLoop.sendMessage(widget, msg); }); } /** * A message handler invoked on a `'before-show'` message. * * #### Notes * The default implementation of this method forwards the message to * all non-hidden widgets. It assumes all widget nodes are attached * to the parent widget node. * * This may be reimplemented by subclasses as needed. */ protected onBeforeShow(msg: Message): void { each(this, widget => { if (!widget.isHidden) { MessageLoop.sendMessage(widget, msg); } }); } /** * A message handler invoked on an `'after-show'` message. * * #### Notes * The default implementation of this method forwards the message to * all non-hidden widgets. It assumes all widget nodes are attached * to the parent widget node. * * This may be reimplemented by subclasses as needed. */ protected onAfterShow(msg: Message): void { each(this, widget => { if (!widget.isHidden) { MessageLoop.sendMessage(widget, msg); } }); } /** * A message handler invoked on a `'before-hide'` message. * * #### Notes * The default implementation of this method forwards the message to * all non-hidden widgets. It assumes all widget nodes are attached * to the parent widget node. * * This may be reimplemented by subclasses as needed. */ protected onBeforeHide(msg: Message): void { each(this, widget => { if (!widget.isHidden) { MessageLoop.sendMessage(widget, msg); } }); } /** * A message handler invoked on an `'after-hide'` message. * * #### Notes * The default implementation of this method forwards the message to * all non-hidden widgets. It assumes all widget nodes are attached * to the parent widget node. * * This may be reimplemented by subclasses as needed. */ protected onAfterHide(msg: Message): void { each(this, widget => { if (!widget.isHidden) { MessageLoop.sendMessage(widget, msg); } }); } /** * A message handler invoked on a `'child-removed'` message. * * #### Notes * This will remove the child widget from the layout. * * Subclasses should **not** typically reimplement this method. */ protected onChildRemoved(msg: Widget.ChildMessage): void { this.removeWidget(msg.child); } /** * A message handler invoked on a `'fit-request'` message. * * #### Notes * The default implementation of this handler is a no-op. */ protected onFitRequest(msg: Message): void {} /** * A message handler invoked on a `'child-shown'` message. * * #### Notes * The default implementation of this handler is a no-op. */ protected onChildShown(msg: Widget.ChildMessage): void {} /** * A message handler invoked on a `'child-hidden'` message. * * #### Notes * The default implementation of this handler is a no-op. */ protected onChildHidden(msg: Widget.ChildMessage): void {} private _disposed = false; private _fitPolicy: Layout.FitPolicy; private _parent: Widget | null = null; } /** * The namespace for the `Layout` class statics. */ export namespace Layout { /** * A type alias for the layout fit policy. * * #### Notes * The fit policy controls the computed size constraints which are * applied to the parent widget by the layout. * * Some layout implementations may ignore the fit policy. */ export type FitPolicy = | /** * No size constraint will be applied to the parent widget. */ 'set-no-constraint' /** * The computed min size will be applied to the parent widget. */ | 'set-min-size'; /** * An options object for initializing a layout. */ export interface IOptions { /** * The fit policy for the layout. * * The default is `'set-min-size'`. */ fitPolicy?: FitPolicy; } /** * A type alias for the horizontal alignment of a widget. */ export type HorizontalAlignment = 'left' | 'center' | 'right'; /** * A type alias for the vertical alignment of a widget. */ export type VerticalAlignment = 'top' | 'center' | 'bottom'; /** * Get the horizontal alignment for a widget. * * @param widget - The widget of interest. * * @returns The horizontal alignment for the widget. * * #### Notes * If the layout width allocated to a widget is larger than its max * width, the horizontal alignment controls how the widget is placed * within the extra horizontal space. * * If the allocated width is less than the widget's max width, the * horizontal alignment has no effect. * * Some layout implementations may ignore horizontal alignment. */ export function getHorizontalAlignment(widget: Widget): HorizontalAlignment { return Private.horizontalAlignmentProperty.get(widget); } /** * Set the horizontal alignment for a widget. * * @param widget - The widget of interest. * * @param value - The value for the horizontal alignment. * * #### Notes * If the layout width allocated to a widget is larger than its max * width, the horizontal alignment controls how the widget is placed * within the extra horizontal space. * * If the allocated width is less than the widget's max width, the * horizontal alignment has no effect. * * Some layout implementations may ignore horizontal alignment. * * Changing the horizontal alignment will post an `update-request` * message to widget's parent, provided the parent has a layout * installed. */ export function setHorizontalAlignment( widget: Widget, value: HorizontalAlignment ): void { Private.horizontalAlignmentProperty.set(widget, value); } /** * Get the vertical alignment for a widget. * * @param widget - The widget of interest. * * @returns The vertical alignment for the widget. * * #### Notes * If the layout height allocated to a widget is larger than its max * height, the vertical alignment controls how the widget is placed * within the extra vertical space. * * If the allocated height is less than the widget's max height, the * vertical alignment has no effect. * * Some layout implementations may ignore vertical alignment. */ export function getVerticalAlignment(widget: Widget): VerticalAlignment { return Private.verticalAlignmentProperty.get(widget); } /** * Set the vertical alignment for a widget. * * @param widget - The widget of interest. * * @param value - The value for the vertical alignment. * * #### Notes * If the layout height allocated to a widget is larger than its max * height, the vertical alignment controls how the widget is placed * within the extra vertical space. * * If the allocated height is less than the widget's max height, the * vertical alignment has no effect. * * Some layout implementations may ignore vertical alignment. * * Changing the horizontal alignment will post an `update-request` * message to widget's parent, provided the parent has a layout * installed. */ export function setVerticalAlignment( widget: Widget, value: VerticalAlignment ): void { Private.verticalAlignmentProperty.set(widget, value); } } /** * An object which assists in the absolute layout of widgets. * * #### Notes * This class is useful when implementing a layout which arranges its * widgets using absolute positioning. * * This class is used by nearly all of the built-in lumino layouts. */ export class LayoutItem implements IDisposable { /** * Construct a new layout item. * * @param widget - The widget to be managed by the item. * * #### Notes * The widget will be set to absolute positioning. */ constructor(widget: Widget) { this.widget = widget; this.widget.node.style.position = 'absolute'; } /** * Dispose of the the layout item. * * #### Notes * This will reset the positioning of the widget. */ dispose(): void { // Do nothing if the item is already disposed. if (this._disposed) { return; } // Mark the item as disposed. this._disposed = true; // Reset the widget style. let style = this.widget.node.style; style.position = ''; style.top = ''; style.left = ''; style.width = ''; style.height = ''; } /** * The widget managed by the layout item. */ readonly widget: Widget; /** * The computed minimum width of the widget. * * #### Notes * This value can be updated by calling the `fit` method. */ get minWidth(): number { return this._minWidth; } /** * The computed minimum height of the widget. * * #### Notes * This value can be updated by calling the `fit` method. */ get minHeight(): number { return this._minHeight; } /** * The computed maximum width of the widget. * * #### Notes * This value can be updated by calling the `fit` method. */ get maxWidth(): number { return this._maxWidth; } /** * The computed maximum height of the widget. * * #### Notes * This value can be updated by calling the `fit` method. */ get maxHeight(): number { return this._maxHeight; } /** * Whether the layout item is disposed. */ get isDisposed(): boolean { return this._disposed; } /** * Whether the managed widget is hidden. */ get isHidden(): boolean { return this.widget.isHidden; } /** * Whether the managed widget is visible. */ get isVisible(): boolean { return this.widget.isVisible; } /** * Whether the managed widget is attached. */ get isAttached(): boolean { return this.widget.isAttached; } /** * Update the computed size limits of the managed widget. */ fit(): void { let limits = ElementExt.sizeLimits(this.widget.node); this._minWidth = limits.minWidth; this._minHeight = limits.minHeight; this._maxWidth = limits.maxWidth; this._maxHeight = limits.maxHeight; } /** * Update the position and size of the managed widget. * * @param left - The left edge position of the layout box. * * @param top - The top edge position of the layout box. * * @param width - The width of the layout box. * * @param height - The height of the layout box. */ update(left: number, top: number, width: number, height: number): void { // Clamp the size to the computed size limits. let clampW = Math.max(this._minWidth, Math.min(width, this._maxWidth)); let clampH = Math.max(this._minHeight, Math.min(height, this._maxHeight)); // Adjust the left edge for the horizontal alignment, if needed. if (clampW < width) { switch (Layout.getHorizontalAlignment(this.widget)) { case 'left': break; case 'center': left += (width - clampW) / 2; break; case 'right': left += width - clampW; break; default: throw 'unreachable'; } } // Adjust the top edge for the vertical alignment, if needed. if (clampH < height) { switch (Layout.getVerticalAlignment(this.widget)) { case 'top': break; case 'center': top += (height - clampH) / 2; break; case 'bottom': top += height - clampH; break; default: throw 'unreachable'; } } // Set up the resize variables. let resized = false; let style = this.widget.node.style; // Update the top edge of the widget if needed. if (this._top !== top) { this._top = top; style.top = `${top}px`; } // Update the left edge of the widget if needed. if (this._left !== left) { this._left = left; style.left = `${left}px`; } // Update the width of the widget if needed. if (this._width !== clampW) { resized = true; this._width = clampW; style.width = `${clampW}px`; } // Update the height of the widget if needed. if (this._height !== clampH) { resized = true; this._height = clampH; style.height = `${clampH}px`; } // Send a resize message to the widget if needed. if (resized) { let msg = new Widget.ResizeMessage(clampW, clampH); MessageLoop.sendMessage(this.widget, msg); } } private _top = NaN; private _left = NaN; private _width = NaN; private _height = NaN; private _minWidth = 0; private _minHeight = 0; private _maxWidth = Infinity; private _maxHeight = Infinity; private _disposed = false; } /** * The namespace for the module implementation details. */ namespace Private { /** * The attached property for a widget horizontal alignment. */ export const horizontalAlignmentProperty = new AttachedProperty< Widget, Layout.HorizontalAlignment >({ name: 'horizontalAlignment', create: () => 'center', changed: onAlignmentChanged }); /** * The attached property for a widget vertical alignment. */ export const verticalAlignmentProperty = new AttachedProperty< Widget, Layout.VerticalAlignment >({ name: 'verticalAlignment', create: () => 'top', changed: onAlignmentChanged }); /** * The change handler for the attached alignment properties. */ function onAlignmentChanged(child: Widget): void { if (child.parent && child.parent.layout) { child.parent.update(); } } } lumino-2021.12.13/packages/widgets/src/menu.ts000066400000000000000000001402371415564225700207570ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { ArrayExt } from '@lumino/algorithm'; import { CommandRegistry } from '@lumino/commands'; import { JSONExt, ReadonlyJSONObject } from '@lumino/coreutils'; import { ElementExt } from '@lumino/domutils'; import { getKeyboardLayout } from '@lumino/keyboard'; import { Message, MessageLoop } from '@lumino/messaging'; import { ISignal, Signal } from '@lumino/signaling'; import { ARIAAttrNames, ElementARIAAttrs, ElementDataset, h, VirtualDOM, VirtualElement } from '@lumino/virtualdom'; import { Widget } from './widget'; /** * A widget which displays items as a canonical menu. */ export class Menu extends Widget { /** * Construct a new menu. * * @param options - The options for initializing the menu. */ constructor(options: Menu.IOptions) { super({ node: Private.createNode() }); this.addClass('lm-Menu'); /* */ this.addClass('p-Menu'); /* */ this.setFlag(Widget.Flag.DisallowLayout); this.commands = options.commands; this.renderer = options.renderer || Menu.defaultRenderer; } /** * Dispose of the resources held by the menu. */ dispose(): void { this.close(); this._items.length = 0; super.dispose(); } /** * A signal emitted just before the menu is closed. * * #### Notes * This signal is emitted when the menu receives a `'close-request'` * message, just before it removes itself from the DOM. * * This signal is not emitted if the menu is already detached from * the DOM when it receives the `'close-request'` message. */ get aboutToClose(): ISignal { return this._aboutToClose; } /** * A signal emitted when a new menu is requested by the user. * * #### Notes * This signal is emitted whenever the user presses the right or left * arrow keys, and a submenu cannot be opened or closed in response. * * This signal is useful when implementing menu bars in order to open * the next or previous menu in response to a user key press. * * This signal is only emitted for the root menu in a hierarchy. */ get menuRequested(): ISignal { return this._menuRequested; } /** * The command registry used by the menu. */ readonly commands: CommandRegistry; /** * The renderer used by the menu. */ readonly renderer: Menu.IRenderer; /** * The parent menu of the menu. * * #### Notes * This is `null` unless the menu is an open submenu. */ get parentMenu(): Menu | null { return this._parentMenu; } /** * The child menu of the menu. * * #### Notes * This is `null` unless the menu has an open submenu. */ get childMenu(): Menu | null { return this._childMenu; } /** * The root menu of the menu hierarchy. */ get rootMenu(): Menu { // eslint-disable-next-line @typescript-eslint/no-this-alias let menu: Menu = this; while (menu._parentMenu) { menu = menu._parentMenu; } return menu; } /** * The leaf menu of the menu hierarchy. */ get leafMenu(): Menu { // eslint-disable-next-line @typescript-eslint/no-this-alias let menu: Menu = this; while (menu._childMenu) { menu = menu._childMenu; } return menu; } /** * The menu content node. * * #### Notes * This is the node which holds the menu item nodes. * * Modifying this node directly can lead to undefined behavior. */ get contentNode(): HTMLUListElement { return this.node.getElementsByClassName( 'lm-Menu-content' )[0] as HTMLUListElement; } /** * Get the currently active menu item. */ get activeItem(): Menu.IItem | null { return this._items[this._activeIndex] || null; } /** * Set the currently active menu item. * * #### Notes * If the item cannot be activated, the item will be set to `null`. */ set activeItem(value: Menu.IItem | null) { this.activeIndex = value ? this._items.indexOf(value) : -1; } /** * Get the index of the currently active menu item. * * #### Notes * This will be `-1` if no menu item is active. */ get activeIndex(): number { return this._activeIndex; } /** * Set the index of the currently active menu item. * * #### Notes * If the item cannot be activated, the index will be set to `-1`. */ set activeIndex(value: number) { // Adjust the value for an out of range index. if (value < 0 || value >= this._items.length) { value = -1; } // Ensure the item can be activated. if (value !== -1 && !Private.canActivate(this._items[value])) { value = -1; } // Bail if the index will not change. if (this._activeIndex === value) { return; } // Update the active index. this._activeIndex = value; // Make active element in focus if ( this._activeIndex >= 0 && this.contentNode.childNodes[this._activeIndex] ) { (this.contentNode.childNodes[this._activeIndex] as HTMLElement).focus(); } // schedule an update of the items. this.update(); } /** * A read-only array of the menu items in the menu. */ get items(): ReadonlyArray { return this._items; } /** * Activate the next selectable item in the menu. * * #### Notes * If no item is selectable, the index will be set to `-1`. */ activateNextItem(): void { let n = this._items.length; let ai = this._activeIndex; let start = ai < n - 1 ? ai + 1 : 0; let stop = start === 0 ? n - 1 : start - 1; this.activeIndex = ArrayExt.findFirstIndex( this._items, Private.canActivate, start, stop ); } /** * Activate the previous selectable item in the menu. * * #### Notes * If no item is selectable, the index will be set to `-1`. */ activatePreviousItem(): void { let n = this._items.length; let ai = this._activeIndex; let start = ai <= 0 ? n - 1 : ai - 1; let stop = start === n - 1 ? 0 : start + 1; this.activeIndex = ArrayExt.findLastIndex( this._items, Private.canActivate, start, stop ); } /** * Trigger the active menu item. * * #### Notes * If the active item is a submenu, it will be opened and the first * item will be activated. * * If the active item is a command, the command will be executed. * * If the menu is not attached, this is a no-op. * * If there is no active item, this is a no-op. */ triggerActiveItem(): void { // Bail if the menu is not attached. if (!this.isAttached) { return; } // Bail if there is no active item. let item = this.activeItem; if (!item) { return; } // Cancel the pending timers. this._cancelOpenTimer(); this._cancelCloseTimer(); // If the item is a submenu, open it. if (item.type === 'submenu') { this._openChildMenu(true); return; } // Close the root menu before executing the command. this.rootMenu.close(); // Execute the command for the item. let { command, args } = item; if (this.commands.isEnabled(command, args)) { this.commands.execute(command, args); } else { console.log(`Command '${command}' is disabled.`); } } /** * Add a menu item to the end of the menu. * * @param options - The options for creating the menu item. * * @returns The menu item added to the menu. */ addItem(options: Menu.IItemOptions): Menu.IItem { return this.insertItem(this._items.length, options); } /** * Insert a menu item into the menu at the specified index. * * @param index - The index at which to insert the item. * * @param options - The options for creating the menu item. * * @returns The menu item added to the menu. * * #### Notes * The index will be clamped to the bounds of the items. */ insertItem(index: number, options: Menu.IItemOptions): Menu.IItem { // Close the menu if it's attached. if (this.isAttached) { this.close(); } // Reset the active index. this.activeIndex = -1; // Clamp the insert index to the array bounds. let i = Math.max(0, Math.min(index, this._items.length)); // Create the item for the options. let item = Private.createItem(this, options); // Insert the item into the array. ArrayExt.insert(this._items, i, item); // Schedule an update of the items. this.update(); // Return the item added to the menu. return item; } /** * Remove an item from the menu. * * @param item - The item to remove from the menu. * * #### Notes * This is a no-op if the item is not in the menu. */ removeItem(item: Menu.IItem): void { this.removeItemAt(this._items.indexOf(item)); } /** * Remove the item at a given index from the menu. * * @param index - The index of the item to remove. * * #### Notes * This is a no-op if the index is out of range. */ removeItemAt(index: number): void { // Close the menu if it's attached. if (this.isAttached) { this.close(); } // Reset the active index. this.activeIndex = -1; // Remove the item from the array. let item = ArrayExt.removeAt(this._items, index); // Bail if the index is out of range. if (!item) { return; } // Schedule an update of the items. this.update(); } /** * Remove all menu items from the menu. */ clearItems(): void { // Close the menu if it's attached. if (this.isAttached) { this.close(); } // Reset the active index. this.activeIndex = -1; // Bail if there is nothing to remove. if (this._items.length === 0) { return; } // Clear the items. this._items.length = 0; // Schedule an update of the items. this.update(); } /** * Open the menu at the specified location. * * @param x - The client X coordinate of the menu location. * * @param y - The client Y coordinate of the menu location. * * @param options - The additional options for opening the menu. * * #### Notes * The menu will be opened at the given location unless it will not * fully fit on the screen. If it will not fit, it will be adjusted * to fit naturally on the screen. * * This is a no-op if the menu is already attached to the DOM. */ open(x: number, y: number, options: Menu.IOpenOptions = {}): void { // Bail early if the menu is already attached. if (this.isAttached) { return; } // Extract the position options. let forceX = options.forceX || false; let forceY = options.forceY || false; // Open the menu as a root menu. Private.openRootMenu(this, x, y, forceX, forceY); // Activate the menu to accept keyboard input. this.activate(); } /** * Handle the DOM events for the menu. * * @param event - The DOM event sent to the menu. * * #### Notes * This method implements the DOM `EventListener` interface and is * called in response to events on the menu's DOM nodes. It should * not be called directly by user code. */ handleEvent(event: Event): void { switch (event.type) { case 'keydown': this._evtKeyDown(event as KeyboardEvent); break; case 'mouseup': this._evtMouseUp(event as MouseEvent); break; case 'mousemove': this._evtMouseMove(event as MouseEvent); break; case 'mouseenter': this._evtMouseEnter(event as MouseEvent); break; case 'mouseleave': this._evtMouseLeave(event as MouseEvent); break; case 'mousedown': this._evtMouseDown(event as MouseEvent); break; case 'contextmenu': event.preventDefault(); event.stopPropagation(); break; } } /** * A message handler invoked on a `'before-attach'` message. */ protected onBeforeAttach(msg: Message): void { this.node.addEventListener('keydown', this); this.node.addEventListener('mouseup', this); this.node.addEventListener('mousemove', this); this.node.addEventListener('mouseenter', this); this.node.addEventListener('mouseleave', this); this.node.addEventListener('contextmenu', this); document.addEventListener('mousedown', this, true); } /** * A message handler invoked on an `'after-detach'` message. */ protected onAfterDetach(msg: Message): void { this.node.removeEventListener('keydown', this); this.node.removeEventListener('mouseup', this); this.node.removeEventListener('mousemove', this); this.node.removeEventListener('mouseenter', this); this.node.removeEventListener('mouseleave', this); this.node.removeEventListener('contextmenu', this); document.removeEventListener('mousedown', this, true); } /** * A message handler invoked on an `'activate-request'` message. */ protected onActivateRequest(msg: Message): void { if (this.isAttached) { this.node.focus(); } } /** * A message handler invoked on an `'update-request'` message. */ protected onUpdateRequest(msg: Message): void { let items = this._items; let renderer = this.renderer; let activeIndex = this._activeIndex; let collapsedFlags = Private.computeCollapsed(items); let content = new Array(items.length); for (let i = 0, n = items.length; i < n; ++i) { let item = items[i]; let active = i === activeIndex; let collapsed = collapsedFlags[i]; content[i] = renderer.renderItem({ item, active, collapsed, onfocus: () => { this.activeIndex = i; } }); } VirtualDOM.render(content, this.contentNode); } /** * A message handler invoked on a `'close-request'` message. */ protected onCloseRequest(msg: Message): void { // Cancel the pending timers. this._cancelOpenTimer(); this._cancelCloseTimer(); // Reset the active index. this.activeIndex = -1; // Close any open child menu. let childMenu = this._childMenu; if (childMenu) { this._childIndex = -1; this._childMenu = null; childMenu._parentMenu = null; childMenu.close(); } // Remove this menu from its parent and activate the parent. let parentMenu = this._parentMenu; if (parentMenu) { this._parentMenu = null; parentMenu._childIndex = -1; parentMenu._childMenu = null; parentMenu.activate(); } // Emit the `aboutToClose` signal if the menu is attached. if (this.isAttached) { this._aboutToClose.emit(undefined); } // Finish closing the menu. super.onCloseRequest(msg); } /** * Handle the `'keydown'` event for the menu. * * #### Notes * This listener is attached to the menu node. */ private _evtKeyDown(event: KeyboardEvent): void { // A menu handles all keydown events. event.preventDefault(); event.stopPropagation(); // Fetch the key code for the event. let kc = event.keyCode; // Enter if (kc === 13) { this.triggerActiveItem(); return; } // Escape if (kc === 27) { this.close(); return; } // Left Arrow if (kc === 37) { if (this._parentMenu) { this.close(); } else { this._menuRequested.emit('previous'); } return; } // Up Arrow if (kc === 38) { this.activatePreviousItem(); return; } // Right Arrow if (kc === 39) { let item = this.activeItem; if (item && item.type === 'submenu') { this.triggerActiveItem(); } else { this.rootMenu._menuRequested.emit('next'); } return; } // Down Arrow if (kc === 40) { this.activateNextItem(); return; } // Get the pressed key character. let key = getKeyboardLayout().keyForKeydownEvent(event); // Bail if the key is not valid. if (!key) { return; } // Search for the next best matching mnemonic item. let start = this._activeIndex + 1; let result = Private.findMnemonic(this._items, key, start); // Handle the requested mnemonic based on the search results. // If exactly one mnemonic is matched, that item is triggered. // Otherwise, the next mnemonic is activated if available, // followed by the auto mnemonic if available. if (result.index !== -1 && !result.multiple) { this.activeIndex = result.index; this.triggerActiveItem(); } else if (result.index !== -1) { this.activeIndex = result.index; } else if (result.auto !== -1) { this.activeIndex = result.auto; } } /** * Handle the `'mouseup'` event for the menu. * * #### Notes * This listener is attached to the menu node. */ private _evtMouseUp(event: MouseEvent): void { if (event.button !== 0) { return; } event.preventDefault(); event.stopPropagation(); this.triggerActiveItem(); } /** * Handle the `'mousemove'` event for the menu. * * #### Notes * This listener is attached to the menu node. */ private _evtMouseMove(event: MouseEvent): void { // Hit test the item nodes for the item under the mouse. let index = ArrayExt.findFirstIndex(this.contentNode.children, node => { return ElementExt.hitTest(node, event.clientX, event.clientY); }); // Bail early if the mouse is already over the active index. if (index === this._activeIndex) { return; } // Update and coerce the active index. this.activeIndex = index; index = this.activeIndex; // If the index is the current child index, cancel the timers. if (index === this._childIndex) { this._cancelOpenTimer(); this._cancelCloseTimer(); return; } // If a child menu is currently open, start the close timer. if (this._childIndex !== -1) { this._startCloseTimer(); } // Cancel the open timer to give a full delay for opening. this._cancelOpenTimer(); // Bail if the active item is not a valid submenu item. let item = this.activeItem; if (!item || item.type !== 'submenu' || !item.submenu) { return; } // Start the open timer to open the active item submenu. this._startOpenTimer(); } /** * Handle the `'mouseenter'` event for the menu. * * #### Notes * This listener is attached to the menu node. */ private _evtMouseEnter(event: MouseEvent): void { // Synchronize the active ancestor items. for (let menu = this._parentMenu; menu; menu = menu._parentMenu) { menu._cancelOpenTimer(); menu._cancelCloseTimer(); menu.activeIndex = menu._childIndex; } } /** * Handle the `'mouseleave'` event for the menu. * * #### Notes * This listener is attached to the menu node. */ private _evtMouseLeave(event: MouseEvent): void { // Cancel any pending submenu opening. this._cancelOpenTimer(); // If there is no open child menu, just reset the active index. if (!this._childMenu) { this.activeIndex = -1; return; } // If the mouse is over the child menu, cancel the close timer. let { clientX, clientY } = event; if (ElementExt.hitTest(this._childMenu.node, clientX, clientY)) { this._cancelCloseTimer(); return; } // Otherwise, reset the active index and start the close timer. this.activeIndex = -1; this._startCloseTimer(); } /** * Handle the `'mousedown'` event for the menu. * * #### Notes * This listener is attached to the document node. */ private _evtMouseDown(event: MouseEvent): void { // Bail if the menu is not a root menu. if (this._parentMenu) { return; } // The mouse button which is pressed is irrelevant. If the press // is not on a menu, the entire hierarchy is closed and the event // is allowed to propagate. This allows other code to act on the // event, such as focusing the clicked element. if (Private.hitTestMenus(this, event.clientX, event.clientY)) { event.preventDefault(); event.stopPropagation(); } else { this.close(); } } /** * Open the child menu at the active index immediately. * * If a different child menu is already open, it will be closed, * even if the active item is not a valid submenu. */ private _openChildMenu(activateFirst = false): void { // If the item is not a valid submenu, close the child menu. let item = this.activeItem; if (!item || item.type !== 'submenu' || !item.submenu) { this._closeChildMenu(); return; } // Do nothing if the child menu will not change. let submenu = item.submenu; if (submenu === this._childMenu) { return; } // Ensure the current child menu is closed. this._closeChildMenu(); // Update the private child state. this._childMenu = submenu; this._childIndex = this._activeIndex; // Set the parent menu reference for the child. submenu._parentMenu = this; // Ensure the menu is updated and lookup the item node. MessageLoop.sendMessage(this, Widget.Msg.UpdateRequest); let itemNode = this.contentNode.children[this._activeIndex]; // Open the submenu at the active node. Private.openSubmenu(submenu, itemNode as HTMLElement); // Activate the first item if desired. if (activateFirst) { submenu.activeIndex = -1; submenu.activateNextItem(); } // Activate the child menu. submenu.activate(); } /** * Close the child menu immediately. * * This is a no-op if a child menu is not open. */ private _closeChildMenu(): void { if (this._childMenu) { this._childMenu.close(); } } /** * Start the open timer, unless it is already pending. */ private _startOpenTimer(): void { if (this._openTimerID === 0) { this._openTimerID = window.setTimeout(() => { this._openTimerID = 0; this._openChildMenu(); }, Private.TIMER_DELAY); } } /** * Start the close timer, unless it is already pending. */ private _startCloseTimer(): void { if (this._closeTimerID === 0) { this._closeTimerID = window.setTimeout(() => { this._closeTimerID = 0; this._closeChildMenu(); }, Private.TIMER_DELAY); } } /** * Cancel the open timer, if the timer is pending. */ private _cancelOpenTimer(): void { if (this._openTimerID !== 0) { clearTimeout(this._openTimerID); this._openTimerID = 0; } } /** * Cancel the close timer, if the timer is pending. */ private _cancelCloseTimer(): void { if (this._closeTimerID !== 0) { clearTimeout(this._closeTimerID); this._closeTimerID = 0; } } private _childIndex = -1; private _activeIndex = -1; private _openTimerID = 0; private _closeTimerID = 0; private _items: Menu.IItem[] = []; private _childMenu: Menu | null = null; private _parentMenu: Menu | null = null; private _aboutToClose = new Signal(this); private _menuRequested = new Signal(this); } /** * The namespace for the `Menu` class statics. */ export namespace Menu { /** * An options object for creating a menu. */ export interface IOptions { /** * The command registry for use with the menu. */ commands: CommandRegistry; /** * A custom renderer for use with the menu. * * The default is a shared renderer instance. */ renderer?: IRenderer; } /** * An options object for the `open` method on a menu. */ export interface IOpenOptions { /** * Whether to force the X position of the menu. * * Setting to `true` will disable the logic which repositions the * X coordinate of the menu if it will not fit entirely on screen. * * The default is `false`. */ forceX?: boolean; /** * Whether to force the Y position of the menu. * * Setting to `true` will disable the logic which repositions the * Y coordinate of the menu if it will not fit entirely on screen. * * The default is `false`. */ forceY?: boolean; } /** * A type alias for a menu item type. */ export type ItemType = 'command' | 'submenu' | 'separator'; /** * An options object for creating a menu item. */ export interface IItemOptions { /** * The type of the menu item. * * The default value is `'command'`. */ type?: ItemType; /** * The command to execute when the item is triggered. * * The default value is an empty string. */ command?: string; /** * The arguments for the command. * * The default value is an empty object. */ args?: ReadonlyJSONObject; /** * The submenu for a `'submenu'` type item. * * The default value is `null`. */ submenu?: Menu | null; } /** * An object which represents a menu item. * * #### Notes * Item objects are created automatically by a menu. */ export interface IItem { /** * The type of the menu item. */ readonly type: ItemType; /** * The command to execute when the item is triggered. */ readonly command: string; /** * The arguments for the command. */ readonly args: ReadonlyJSONObject; /** * The submenu for a `'submenu'` type item. */ readonly submenu: Menu | null; /** * The display label for the menu item. */ readonly label: string; /** * The mnemonic index for the menu item. */ readonly mnemonic: number; /** * The icon renderer for the menu item. */ readonly icon: | VirtualElement.IRenderer | undefined /* */ | string /* */; /** * The icon class for the menu item. */ readonly iconClass: string; /** * The icon label for the menu item. */ readonly iconLabel: string; /** * The display caption for the menu item. */ readonly caption: string; /** * The extra class name for the menu item. */ readonly className: string; /** * The dataset for the menu item. */ readonly dataset: CommandRegistry.Dataset; /** * Whether the menu item is enabled. */ readonly isEnabled: boolean; /** * Whether the menu item is toggled. */ readonly isToggled: boolean; /** * Whether the menu item is visible. */ readonly isVisible: boolean; /** * The key binding for the menu item. */ readonly keyBinding: CommandRegistry.IKeyBinding | null; } /** * An object which holds the data to render a menu item. */ export interface IRenderData { /** * The item to be rendered. */ readonly item: IItem; /** * Whether the item is the active item. */ readonly active: boolean; /** * Whether the item should be collapsed. */ readonly collapsed: boolean; /** * Handler for when element is in focus. */ readonly onfocus?: () => void; } /** * A renderer for use with a menu. */ export interface IRenderer { /** * Render the virtual element for a menu item. * * @param data - The data to use for rendering the item. * * @returns A virtual element representing the item. */ renderItem(data: IRenderData): VirtualElement; } /** * The default implementation of `IRenderer`. * * #### Notes * Subclasses are free to reimplement rendering methods as needed. */ export class Renderer implements IRenderer { /** * Render the virtual element for a menu item. * * @param data - The data to use for rendering the item. * * @returns A virtual element representing the item. */ renderItem(data: IRenderData): VirtualElement { let className = this.createItemClass(data); let dataset = this.createItemDataset(data); let aria = this.createItemARIA(data); return h.li( { className, dataset, tabindex: '0', onfocus: data.onfocus, ...aria }, this.renderIcon(data), this.renderLabel(data), this.renderShortcut(data), this.renderSubmenu(data) ); } /** * Render the icon element for a menu item. * * @param data - The data to use for rendering the icon. * * @returns A virtual element representing the item icon. */ renderIcon(data: IRenderData): VirtualElement { let className = this.createIconClass(data); /* */ if (typeof data.item.icon === 'string') { return h.div({ className }, data.item.iconLabel); } /* */ // if data.item.icon is undefined, it will be ignored return h.div({ className }, data.item.icon!, data.item.iconLabel); } /** * Render the label element for a menu item. * * @param data - The data to use for rendering the label. * * @returns A virtual element representing the item label. */ renderLabel(data: IRenderData): VirtualElement { let content = this.formatLabel(data); return h.div( { className: 'lm-Menu-itemLabel' + /* */ ' p-Menu-itemLabel' /* */ }, content ); } /** * Render the shortcut element for a menu item. * * @param data - The data to use for rendering the shortcut. * * @returns A virtual element representing the item shortcut. */ renderShortcut(data: IRenderData): VirtualElement { let content = this.formatShortcut(data); return h.div( { className: 'lm-Menu-itemShortcut' + /* */ ' p-Menu-itemShortcut' /* */ }, content ); } /** * Render the submenu icon element for a menu item. * * @param data - The data to use for rendering the submenu icon. * * @returns A virtual element representing the submenu icon. */ renderSubmenu(data: IRenderData): VirtualElement { return h.div({ className: 'lm-Menu-itemSubmenuIcon' + /* */ ' p-Menu-itemSubmenuIcon' /* */ }); } /** * Create the class name for the menu item. * * @param data - The data to use for the class name. * * @returns The full class name for the menu item. */ createItemClass(data: IRenderData): string { // Setup the initial class name. let name = 'lm-Menu-item'; /* */ name += ' p-Menu-item'; /* */ // Add the boolean state classes. if (!data.item.isEnabled) { name += ' lm-mod-disabled'; /* */ name += ' p-mod-disabled'; /* */ } if (data.item.isToggled) { name += ' lm-mod-toggled'; /* */ name += ' p-mod-toggled'; /* */ } if (!data.item.isVisible) { name += ' lm-mod-hidden'; /* */ name += ' p-mod-hidden'; /* */ } if (data.active) { name += ' lm-mod-active'; /* */ name += ' p-mod-active'; /* */ } if (data.collapsed) { name += ' lm-mod-collapsed'; /* */ name += ' p-mod-collapsed'; /* */ } // Add the extra class. let extra = data.item.className; if (extra) { name += ` ${extra}`; } // Return the complete class name. return name; } /** * Create the dataset for the menu item. * * @param data - The data to use for creating the dataset. * * @returns The dataset for the menu item. */ createItemDataset(data: IRenderData): ElementDataset { let result: ElementDataset; let { type, command, dataset } = data.item; if (type === 'command') { result = { ...dataset, type, command }; } else { result = { ...dataset, type }; } return result; } /** * Create the class name for the menu item icon. * * @param data - The data to use for the class name. * * @returns The full class name for the item icon. */ createIconClass(data: IRenderData): string { let name = 'lm-Menu-itemIcon'; /* */ name += ' p-Menu-itemIcon'; /* */ let extra = data.item.iconClass; return extra ? `${name} ${extra}` : name; } /** * Create the aria attributes for menu item. * * @param data - The data to use for the aria attributes. * * @returns The aria attributes object for the item. */ createItemARIA(data: IRenderData): ElementARIAAttrs { let aria: { [T in ARIAAttrNames]?: string } = {}; switch (data.item.type) { case 'separator': aria.role = 'presentation'; break; case 'submenu': aria['aria-haspopup'] = 'true'; if (!data.item.isEnabled) { aria['aria-disabled'] = 'true'; } break; default: if (!data.item.isEnabled) { aria['aria-disabled'] = 'true'; } aria.role = 'menuitem'; } return aria; } /** * Create the render content for the label node. * * @param data - The data to use for the label content. * * @returns The content to add to the label node. */ formatLabel(data: IRenderData): h.Child { // Fetch the label text and mnemonic index. let { label, mnemonic } = data.item; // If the index is out of range, do not modify the label. if (mnemonic < 0 || mnemonic >= label.length) { return label; } // Split the label into parts. let prefix = label.slice(0, mnemonic); let suffix = label.slice(mnemonic + 1); let char = label[mnemonic]; // Wrap the mnemonic character in a span. let span = h.span( { className: 'lm-Menu-itemMnemonic' + /* */ ' p-Menu-itemMnemonic' /* */ }, char ); // Return the content parts. return [prefix, span, suffix]; } /** * Create the render content for the shortcut node. * * @param data - The data to use for the shortcut content. * * @returns The content to add to the shortcut node. */ formatShortcut(data: IRenderData): h.Child { let kb = data.item.keyBinding; return kb ? kb.keys.map(CommandRegistry.formatKeystroke).join(', ') : null; } } /** * The default `Renderer` instance. */ export const defaultRenderer = new Renderer(); } /** * The namespace for the module implementation details. */ namespace Private { /** * The ms delay for opening and closing a submenu. */ export const TIMER_DELAY = 300; /** * The horizontal pixel overlap for an open submenu. */ export const SUBMENU_OVERLAP = 3; /** * Create the DOM node for a menu. */ export function createNode(): HTMLDivElement { let node = document.createElement('div'); let content = document.createElement('ul'); content.className = 'lm-Menu-content'; /* */ content.classList.add('p-Menu-content'); /* */ node.appendChild(content); content.setAttribute('role', 'menu'); node.tabIndex = 0; return node; } /** * Test whether a menu item can be activated. */ export function canActivate(item: Menu.IItem): boolean { return item.type !== 'separator' && item.isEnabled && item.isVisible; } /** * Create a new menu item for an owner menu. */ export function createItem( owner: Menu, options: Menu.IItemOptions ): Menu.IItem { return new MenuItem(owner.commands, options); } /** * Hit test a menu hierarchy starting at the given root. */ export function hitTestMenus(menu: Menu, x: number, y: number): boolean { for (let temp: Menu | null = menu; temp; temp = temp.childMenu) { if (ElementExt.hitTest(temp.node, x, y)) { return true; } } return false; } /** * Compute which extra separator items should be collapsed. */ export function computeCollapsed( items: ReadonlyArray ): boolean[] { // Allocate the return array and fill it with `false`. let result = new Array(items.length); ArrayExt.fill(result, false); // Collapse the leading separators. let k1 = 0; let n = items.length; for (; k1 < n; ++k1) { let item = items[k1]; if (!item.isVisible) { continue; } if (item.type !== 'separator') { break; } result[k1] = true; } // Hide the trailing separators. let k2 = n - 1; for (; k2 >= 0; --k2) { let item = items[k2]; if (!item.isVisible) { continue; } if (item.type !== 'separator') { break; } result[k2] = true; } // Hide the remaining consecutive separators. let hide = false; while (++k1 < k2) { let item = items[k1]; if (!item.isVisible) { continue; } if (item.type !== 'separator') { hide = false; } else if (hide) { result[k1] = true; } else { hide = true; } } // Return the resulting flags. return result; } /** * Open a menu as a root menu at the target location. */ export function openRootMenu( menu: Menu, x: number, y: number, forceX: boolean, forceY: boolean ): void { // Ensure the menu is updated before attaching and measuring. MessageLoop.sendMessage(menu, Widget.Msg.UpdateRequest); // Get the current position and size of the main viewport. let px = window.pageXOffset; let py = window.pageYOffset; let cw = document.documentElement.clientWidth; let ch = document.documentElement.clientHeight; // Compute the maximum allowed height for the menu. let maxHeight = ch - (forceY ? y : 0); // Fetch common variables. let node = menu.node; let style = node.style; // Clear the menu geometry and prepare it for measuring. style.top = ''; style.left = ''; style.width = ''; style.height = ''; style.visibility = 'hidden'; style.maxHeight = `${maxHeight}px`; // Attach the menu to the document. Widget.attach(menu, document.body); // Measure the size of the menu. let { width, height } = node.getBoundingClientRect(); // Adjust the X position of the menu to fit on-screen. if (!forceX && x + width > px + cw) { x = px + cw - width; } // Adjust the Y position of the menu to fit on-screen. if (!forceY && y + height > py + ch) { if (y > py + ch) { y = py + ch - height; } else { y = y - height; } } // Update the position of the menu to the computed position. style.top = `${Math.max(0, y)}px`; style.left = `${Math.max(0, x)}px`; // Finally, make the menu visible on the screen. style.visibility = ''; } /** * Open a menu as a submenu using an item node for positioning. */ export function openSubmenu(submenu: Menu, itemNode: HTMLElement): void { // Ensure the menu is updated before opening. MessageLoop.sendMessage(submenu, Widget.Msg.UpdateRequest); // Get the current position and size of the main viewport. let px = window.pageXOffset; let py = window.pageYOffset; let cw = document.documentElement.clientWidth; let ch = document.documentElement.clientHeight; // Compute the maximum allowed height for the menu. let maxHeight = ch; // Fetch common variables. let node = submenu.node; let style = node.style; // Clear the menu geometry and prepare it for measuring. style.top = ''; style.left = ''; style.width = ''; style.height = ''; style.visibility = 'hidden'; style.maxHeight = `${maxHeight}px`; // Attach the menu to the document. Widget.attach(submenu, document.body); // Measure the size of the menu. let { width, height } = node.getBoundingClientRect(); // Compute the box sizing for the menu. let box = ElementExt.boxSizing(submenu.node); // Get the bounding rect for the target item node. let itemRect = itemNode.getBoundingClientRect(); // Compute the target X position. let x = itemRect.right - SUBMENU_OVERLAP; // Adjust the X position to fit on the screen. if (x + width > px + cw) { x = itemRect.left + SUBMENU_OVERLAP - width; } // Compute the target Y position. let y = itemRect.top - box.borderTop - box.paddingTop; // Adjust the Y position to fit on the screen. if (y + height > py + ch) { y = itemRect.bottom + box.borderBottom + box.paddingBottom - height; } // Update the position of the menu to the computed position. style.top = `${Math.max(0, y)}px`; style.left = `${Math.max(0, x)}px`; // Finally, make the menu visible on the screen. style.visibility = ''; } /** * The results of a mnemonic search. */ export interface IMnemonicResult { /** * The index of the first matching mnemonic item, or `-1`. */ index: number; /** * Whether multiple mnemonic items matched. */ multiple: boolean; /** * The index of the first auto matched non-mnemonic item. */ auto: number; } /** * Find the best matching mnemonic item. * * The search starts at the given index and wraps around. */ export function findMnemonic( items: ReadonlyArray, key: string, start: number ): IMnemonicResult { // Setup the result variables. let index = -1; let auto = -1; let multiple = false; // Normalize the key to upper case. let upperKey = key.toUpperCase(); // Search the items from the given start index. for (let i = 0, n = items.length; i < n; ++i) { // Compute the wrapped index. let k = (i + start) % n; // Lookup the item let item = items[k]; // Ignore items which cannot be activated. if (!canActivate(item)) { continue; } // Ignore items with an empty label. let label = item.label; if (label.length === 0) { continue; } // Lookup the mnemonic index for the label. let mn = item.mnemonic; // Handle a valid mnemonic index. if (mn >= 0 && mn < label.length) { if (label[mn].toUpperCase() === upperKey) { if (index === -1) { index = k; } else { multiple = true; } } continue; } // Finally, handle the auto index if possible. if (auto === -1 && label[0].toUpperCase() === upperKey) { auto = k; } } // Return the search results. return { index, multiple, auto }; } /** * A concrete implementation of `Menu.IItem`. */ class MenuItem implements Menu.IItem { /** * Construct a new menu item. */ constructor(commands: CommandRegistry, options: Menu.IItemOptions) { this._commands = commands; this.type = options.type || 'command'; this.command = options.command || ''; this.args = options.args || JSONExt.emptyObject; this.submenu = options.submenu || null; } /** * The type of the menu item. */ readonly type: Menu.ItemType; /** * The command to execute when the item is triggered. */ readonly command: string; /** * The arguments for the command. */ readonly args: ReadonlyJSONObject; /** * The submenu for a `'submenu'` type item. */ readonly submenu: Menu | null; /** * The display label for the menu item. */ get label(): string { if (this.type === 'command') { return this._commands.label(this.command, this.args); } if (this.type === 'submenu' && this.submenu) { return this.submenu.title.label; } return ''; } /** * The mnemonic index for the menu item. */ get mnemonic(): number { if (this.type === 'command') { return this._commands.mnemonic(this.command, this.args); } if (this.type === 'submenu' && this.submenu) { return this.submenu.title.mnemonic; } return -1; } /** * The icon renderer for the menu item. */ get icon(): | VirtualElement.IRenderer | undefined /* */ | string /* */ { if (this.type === 'command') { return this._commands.icon(this.command, this.args); } if (this.type === 'submenu' && this.submenu) { return this.submenu.title.icon; } /* */ // alias to icon class if not otherwise defined return this.iconClass; /* */ /* return undefined; */ } /** * The icon class for the menu item. */ get iconClass(): string { if (this.type === 'command') { return this._commands.iconClass(this.command, this.args); } if (this.type === 'submenu' && this.submenu) { return this.submenu.title.iconClass; } return ''; } /** * The icon label for the menu item. */ get iconLabel(): string { if (this.type === 'command') { return this._commands.iconLabel(this.command, this.args); } if (this.type === 'submenu' && this.submenu) { return this.submenu.title.iconLabel; } return ''; } /** * The display caption for the menu item. */ get caption(): string { if (this.type === 'command') { return this._commands.caption(this.command, this.args); } if (this.type === 'submenu' && this.submenu) { return this.submenu.title.caption; } return ''; } /** * The extra class name for the menu item. */ get className(): string { if (this.type === 'command') { return this._commands.className(this.command, this.args); } if (this.type === 'submenu' && this.submenu) { return this.submenu.title.className; } return ''; } /** * The dataset for the menu item. */ get dataset(): CommandRegistry.Dataset { if (this.type === 'command') { return this._commands.dataset(this.command, this.args); } if (this.type === 'submenu' && this.submenu) { return this.submenu.title.dataset; } return {}; } /** * Whether the menu item is enabled. */ get isEnabled(): boolean { if (this.type === 'command') { return this._commands.isEnabled(this.command, this.args); } if (this.type === 'submenu') { return this.submenu !== null; } return true; } /** * Whether the menu item is toggled. */ get isToggled(): boolean { if (this.type === 'command') { return this._commands.isToggled(this.command, this.args); } return false; } /** * Whether the menu item is visible. */ get isVisible(): boolean { if (this.type === 'command') { return this._commands.isVisible(this.command, this.args); } if (this.type === 'submenu') { return this.submenu !== null; } return true; } /** * The key binding for the menu item. */ get keyBinding(): CommandRegistry.IKeyBinding | null { if (this.type === 'command') { let { command, args } = this; return ( ArrayExt.findLastValue(this._commands.keyBindings, kb => { return kb.command === command && JSONExt.deepEqual(kb.args, args); }) || null ); } return null; } private _commands: CommandRegistry; } } lumino-2021.12.13/packages/widgets/src/menubar.ts000066400000000000000000000651521415564225700214460ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { ArrayExt } from '@lumino/algorithm'; import { ElementExt } from '@lumino/domutils'; import { getKeyboardLayout } from '@lumino/keyboard'; import { Message, MessageLoop } from '@lumino/messaging'; import { ElementARIAAttrs, ElementDataset, h, VirtualDOM, VirtualElement } from '@lumino/virtualdom'; import { Menu } from './menu'; import { Title } from './title'; import { Widget } from './widget'; /** * A widget which displays menus as a canonical menu bar. */ export class MenuBar extends Widget { /** * Construct a new menu bar. * * @param options - The options for initializing the menu bar. */ constructor(options: MenuBar.IOptions = {}) { super({ node: Private.createNode() }); this.addClass('lm-MenuBar'); /* */ this.addClass('p-MenuBar'); /* */ this.setFlag(Widget.Flag.DisallowLayout); this.renderer = options.renderer || MenuBar.defaultRenderer; this._forceItemsPosition = options.forceItemsPosition || { forceX: true, forceY: true }; } /** * Dispose of the resources held by the widget. */ dispose(): void { this._closeChildMenu(); this._menus.length = 0; super.dispose(); } /** * The renderer used by the menu bar. */ readonly renderer: MenuBar.IRenderer; /** * The child menu of the menu bar. * * #### Notes * This will be `null` if the menu bar does not have an open menu. */ get childMenu(): Menu | null { return this._childMenu; } /** * Get the menu bar content node. * * #### Notes * This is the node which holds the menu title nodes. * * Modifying this node directly can lead to undefined behavior. */ get contentNode(): HTMLUListElement { return this.node.getElementsByClassName( 'lm-MenuBar-content' )[0] as HTMLUListElement; } /** * Get the currently active menu. */ get activeMenu(): Menu | null { return this._menus[this._activeIndex] || null; } /** * Set the currently active menu. * * #### Notes * If the menu does not exist, the menu will be set to `null`. */ set activeMenu(value: Menu | null) { this.activeIndex = value ? this._menus.indexOf(value) : -1; } /** * Get the index of the currently active menu. * * #### Notes * This will be `-1` if no menu is active. */ get activeIndex(): number { return this._activeIndex; } /** * Set the index of the currently active menu. * * #### Notes * If the menu cannot be activated, the index will be set to `-1`. */ set activeIndex(value: number) { // Adjust the value for an out of range index. if (value < 0 || value >= this._menus.length) { value = -1; } // Bail early if the index will not change. if (this._activeIndex === value) { return; } // Update the active index. this._activeIndex = value; // Update focus to new active index if ( this._activeIndex >= 0 && this.contentNode.childNodes[this._activeIndex] ) { (this.contentNode.childNodes[this._activeIndex] as HTMLElement).focus(); } // Schedule an update of the items. this.update(); } /** * A read-only array of the menus in the menu bar. */ get menus(): ReadonlyArray { return this._menus; } /** * Open the active menu and activate its first menu item. * * #### Notes * If there is no active menu, this is a no-op. */ openActiveMenu(): void { // Bail early if there is no active item. if (this._activeIndex === -1) { return; } // Open the child menu. this._openChildMenu(); // Activate the first item in the child menu. if (this._childMenu) { this._childMenu.activeIndex = -1; this._childMenu.activateNextItem(); } } /** * Add a menu to the end of the menu bar. * * @param menu - The menu to add to the menu bar. * * #### Notes * If the menu is already added to the menu bar, it will be moved. */ addMenu(menu: Menu): void { this.insertMenu(this._menus.length, menu); } /** * Insert a menu into the menu bar at the specified index. * * @param index - The index at which to insert the menu. * * @param menu - The menu to insert into the menu bar. * * #### Notes * The index will be clamped to the bounds of the menus. * * If the menu is already added to the menu bar, it will be moved. */ insertMenu(index: number, menu: Menu): void { // Close the child menu before making changes. this._closeChildMenu(); // Look up the index of the menu. let i = this._menus.indexOf(menu); // Clamp the insert index to the array bounds. let j = Math.max(0, Math.min(index, this._menus.length)); // If the menu is not in the array, insert it. if (i === -1) { // Insert the menu into the array. ArrayExt.insert(this._menus, j, menu); // Add the styling class to the menu. menu.addClass('lm-MenuBar-menu'); /* */ menu.addClass('p-MenuBar-menu'); /* */ // Connect to the menu signals. menu.aboutToClose.connect(this._onMenuAboutToClose, this); menu.menuRequested.connect(this._onMenuMenuRequested, this); menu.title.changed.connect(this._onTitleChanged, this); // Schedule an update of the items. this.update(); // There is nothing more to do. return; } // Otherwise, the menu exists in the array and should be moved. // Adjust the index if the location is at the end of the array. if (j === this._menus.length) { j--; } // Bail if there is no effective move. if (i === j) { return; } // Move the menu to the new locations. ArrayExt.move(this._menus, i, j); // Schedule an update of the items. this.update(); } /** * Remove a menu from the menu bar. * * @param menu - The menu to remove from the menu bar. * * #### Notes * This is a no-op if the menu is not in the menu bar. */ removeMenu(menu: Menu): void { this.removeMenuAt(this._menus.indexOf(menu)); } /** * Remove the menu at a given index from the menu bar. * * @param index - The index of the menu to remove. * * #### Notes * This is a no-op if the index is out of range. */ removeMenuAt(index: number): void { // Close the child menu before making changes. this._closeChildMenu(); // Remove the menu from the array. let menu = ArrayExt.removeAt(this._menus, index); // Bail if the index is out of range. if (!menu) { return; } // Disconnect from the menu signals. menu.aboutToClose.disconnect(this._onMenuAboutToClose, this); menu.menuRequested.disconnect(this._onMenuMenuRequested, this); menu.title.changed.disconnect(this._onTitleChanged, this); // Remove the styling class from the menu. menu.removeClass('lm-MenuBar-menu'); /* */ menu.removeClass('p-MenuBar-menu'); /* */ // Schedule an update of the items. this.update(); } /** * Remove all menus from the menu bar. */ clearMenus(): void { // Bail if there is nothing to remove. if (this._menus.length === 0) { return; } // Close the child menu before making changes. this._closeChildMenu(); // Disconnect from the menu signals and remove the styling class. for (let menu of this._menus) { menu.aboutToClose.disconnect(this._onMenuAboutToClose, this); menu.menuRequested.disconnect(this._onMenuMenuRequested, this); menu.title.changed.disconnect(this._onTitleChanged, this); menu.removeClass('lm-MenuBar-menu'); /* */ menu.removeClass('p-MenuBar-menu'); /* */ } // Clear the menus array. this._menus.length = 0; // Schedule an update of the items. this.update(); } /** * Handle the DOM events for the menu bar. * * @param event - The DOM event sent to the menu bar. * * #### Notes * This method implements the DOM `EventListener` interface and is * called in response to events on the menu bar's DOM nodes. It * should not be called directly by user code. */ handleEvent(event: Event): void { switch (event.type) { case 'keydown': this._evtKeyDown(event as KeyboardEvent); break; case 'mousedown': this._evtMouseDown(event as MouseEvent); break; case 'mousemove': this._evtMouseMove(event as MouseEvent); break; case 'mouseleave': this._evtMouseLeave(event as MouseEvent); break; case 'contextmenu': event.preventDefault(); event.stopPropagation(); break; } } /** * A message handler invoked on a `'before-attach'` message. */ protected onBeforeAttach(msg: Message): void { this.node.addEventListener('keydown', this); this.node.addEventListener('mousedown', this); this.node.addEventListener('mousemove', this); this.node.addEventListener('mouseleave', this); this.node.addEventListener('contextmenu', this); } /** * A message handler invoked on an `'after-detach'` message. */ protected onAfterDetach(msg: Message): void { this.node.removeEventListener('keydown', this); this.node.removeEventListener('mousedown', this); this.node.removeEventListener('mousemove', this); this.node.removeEventListener('mouseleave', this); this.node.removeEventListener('contextmenu', this); this._closeChildMenu(); } /** * A message handler invoked on an `'activate-request'` message. */ protected onActivateRequest(msg: Message): void { if (this.isAttached) { this.node.focus(); } } /** * A message handler invoked on an `'update-request'` message. */ protected onUpdateRequest(msg: Message): void { let menus = this._menus; let renderer = this.renderer; let activeIndex = this._activeIndex; let content = new Array(menus.length); for (let i = 0, n = menus.length; i < n; ++i) { let title = menus[i].title; let active = i === activeIndex; content[i] = renderer.renderItem({ title, active, onfocus: () => { this.activeIndex = i; } }); } VirtualDOM.render(content, this.contentNode); } /** * Handle the `'keydown'` event for the menu bar. */ private _evtKeyDown(event: KeyboardEvent): void { // A menu bar handles all keydown events. event.preventDefault(); event.stopPropagation(); // Fetch the key code for the event. let kc = event.keyCode; // Enter, Up Arrow, Down Arrow if (kc === 13 || kc === 38 || kc === 40) { this.openActiveMenu(); return; } // Escape if (kc === 27) { this._closeChildMenu(); this.activeIndex = -1; this.node.blur(); return; } // Left Arrow if (kc === 37) { let i = this._activeIndex; let n = this._menus.length; this.activeIndex = i === 0 ? n - 1 : i - 1; return; } // Right Arrow if (kc === 39) { let i = this._activeIndex; let n = this._menus.length; this.activeIndex = i === n - 1 ? 0 : i + 1; return; } // Get the pressed key character. let key = getKeyboardLayout().keyForKeydownEvent(event); // Bail if the key is not valid. if (!key) { return; } // Search for the next best matching mnemonic item. let start = this._activeIndex + 1; let result = Private.findMnemonic(this._menus, key, start); // Handle the requested mnemonic based on the search results. // If exactly one mnemonic is matched, that menu is opened. // Otherwise, the next mnemonic is activated if available, // followed by the auto mnemonic if available. if (result.index !== -1 && !result.multiple) { this.activeIndex = result.index; this.openActiveMenu(); } else if (result.index !== -1) { this.activeIndex = result.index; } else if (result.auto !== -1) { this.activeIndex = result.auto; } } /** * Handle the `'mousedown'` event for the menu bar. */ private _evtMouseDown(event: MouseEvent): void { // Bail if the mouse press was not on the menu bar. This can occur // when the document listener is installed for an active menu bar. if (!ElementExt.hitTest(this.node, event.clientX, event.clientY)) { return; } // Stop the propagation of the event. Immediate propagation is // also stopped so that an open menu does not handle the event. event.preventDefault(); event.stopPropagation(); event.stopImmediatePropagation(); // Check if the mouse is over one of the menu items. let index = ArrayExt.findFirstIndex(this.contentNode.children, node => { return ElementExt.hitTest(node, event.clientX, event.clientY); }); // If the press was not on an item, close the child menu. if (index === -1) { this._closeChildMenu(); return; } // If the press was not the left mouse button, do nothing further. if (event.button !== 0) { return; } // Otherwise, toggle the open state of the child menu. if (this._childMenu) { this._closeChildMenu(); this.activeIndex = index; } else { this.activeIndex = index; this._openChildMenu(); } } /** * Handle the `'mousemove'` event for the menu bar. */ private _evtMouseMove(event: MouseEvent): void { // Check if the mouse is over one of the menu items. let index = ArrayExt.findFirstIndex(this.contentNode.children, node => { return ElementExt.hitTest(node, event.clientX, event.clientY); }); // Bail early if the active index will not change. if (index === this._activeIndex) { return; } // Bail early if a child menu is open and the mouse is not over // an item. This allows the child menu to be kept open when the // mouse is over the empty part of the menu bar. if (index === -1 && this._childMenu) { return; } // Update the active index to the hovered item. this.activeIndex = index; // Open the new menu if a menu is already open. if (this._childMenu) { this._openChildMenu(); } } /** * Handle the `'mouseleave'` event for the menu bar. */ private _evtMouseLeave(event: MouseEvent): void { // Reset the active index if there is no open menu. if (!this._childMenu) { this.activeIndex = -1; } } /** * Open the child menu at the active index immediately. * * If a different child menu is already open, it will be closed, * even if there is no active menu. */ private _openChildMenu(): void { // If there is no active menu, close the current menu. let newMenu = this.activeMenu; if (!newMenu) { this._closeChildMenu(); return; } // Bail if there is no effective menu change. let oldMenu = this._childMenu; if (oldMenu === newMenu) { return; } // Swap the internal menu reference. this._childMenu = newMenu; // Close the current menu, or setup for the new menu. if (oldMenu) { oldMenu.close(); } else { this.addClass('lm-mod-active'); /* */ this.addClass('p-mod-active'); /* */ document.addEventListener('mousedown', this, true); } // Ensure the menu bar is updated and look up the item node. MessageLoop.sendMessage(this, Widget.Msg.UpdateRequest); let itemNode = this.contentNode.children[this._activeIndex]; // Get the positioning data for the new menu. let { left, bottom } = (itemNode as HTMLElement).getBoundingClientRect(); // Open the new menu at the computed location. newMenu.open(left, bottom, this._forceItemsPosition); } /** * Close the child menu immediately. * * This is a no-op if a child menu is not open. */ private _closeChildMenu(): void { // Bail if no child menu is open. if (!this._childMenu) { return; } // Remove the active class from the menu bar. this.removeClass('lm-mod-active'); /* */ this.removeClass('p-mod-active'); /* */ // Remove the document listeners. document.removeEventListener('mousedown', this, true); // Clear the internal menu reference. let menu = this._childMenu; this._childMenu = null; // Close the menu. menu.close(); // Reset the active index. this.activeIndex = -1; } /** * Handle the `aboutToClose` signal of a menu. */ private _onMenuAboutToClose(sender: Menu): void { // Bail if the sender is not the child menu. if (sender !== this._childMenu) { return; } // Remove the active class from the menu bar. this.removeClass('lm-mod-active'); /* */ this.removeClass('p-mod-active'); /* */ // Remove the document listeners. document.removeEventListener('mousedown', this, true); // Clear the internal menu reference. this._childMenu = null; // Reset the active index. this.activeIndex = -1; } /** * Handle the `menuRequested` signal of a child menu. */ private _onMenuMenuRequested(sender: Menu, args: 'next' | 'previous'): void { // Bail if the sender is not the child menu. if (sender !== this._childMenu) { return; } // Look up the active index and menu count. let i = this._activeIndex; let n = this._menus.length; // Active the next requested index. switch (args) { case 'next': this.activeIndex = i === n - 1 ? 0 : i + 1; break; case 'previous': this.activeIndex = i === 0 ? n - 1 : i - 1; break; } // Open the active menu. this.openActiveMenu(); } /** * Handle the `changed` signal of a title object. */ private _onTitleChanged(): void { this.update(); } private _activeIndex = -1; private _forceItemsPosition: Menu.IOpenOptions; private _menus: Menu[] = []; private _childMenu: Menu | null = null; } /** * The namespace for the `MenuBar` class statics. */ export namespace MenuBar { /** * An options object for creating a menu bar. */ export interface IOptions { /** * A custom renderer for creating menu bar content. * * The default is a shared renderer instance. */ renderer?: IRenderer; /** * Whether to force the position of the menu. The MenuBar forces the * coordinates of its menus by default. With this option you can disable it. * * Setting to `false` will enable the logic which repositions the * coordinates of the menu if it will not fit entirely on screen. * * The default is `true`. */ forceItemsPosition?: Menu.IOpenOptions; } /** * An object which holds the data to render a menu bar item. */ export interface IRenderData { /** * The title to be rendered. */ readonly title: Title; /** * Whether the item is the active item. */ readonly active: boolean; readonly onfocus?: (event: FocusEvent) => void; } /** * A renderer for use with a menu bar. */ export interface IRenderer { /** * Render the virtual element for a menu bar item. * * @param data - The data to use for rendering the item. * * @returns A virtual element representing the item. */ renderItem(data: IRenderData): VirtualElement; } /** * The default implementation of `IRenderer`. * * #### Notes * Subclasses are free to reimplement rendering methods as needed. */ export class Renderer implements IRenderer { /** * Render the virtual element for a menu bar item. * * @param data - The data to use for rendering the item. * * @returns A virtual element representing the item. */ renderItem(data: IRenderData): VirtualElement { let className = this.createItemClass(data); let dataset = this.createItemDataset(data); let aria = this.createItemARIA(data); return h.li( { className, dataset, tabindex: '0', onfocus: data.onfocus, ...aria }, this.renderIcon(data), this.renderLabel(data) ); } /** * Render the icon element for a menu bar item. * * @param data - The data to use for rendering the icon. * * @returns A virtual element representing the item icon. */ renderIcon(data: IRenderData): VirtualElement { let className = this.createIconClass(data); /* */ if (typeof data.title.icon === 'string') { return h.div({ className }, data.title.iconLabel); } /* */ // if data.title.icon is undefined, it will be ignored return h.div({ className }, data.title.icon!, data.title.iconLabel); } /** * Render the label element for a menu item. * * @param data - The data to use for rendering the label. * * @returns A virtual element representing the item label. */ renderLabel(data: IRenderData): VirtualElement { let content = this.formatLabel(data); return h.div( { className: 'lm-MenuBar-itemLabel' + /* */ ' p-MenuBar-itemLabel' /* */ }, content ); } /** * Create the class name for the menu bar item. * * @param data - The data to use for the class name. * * @returns The full class name for the menu item. */ createItemClass(data: IRenderData): string { let name = 'lm-MenuBar-item'; /* */ name += ' p-MenuBar-item'; /* */ if (data.title.className) { name += ` ${data.title.className}`; } if (data.active) { name += ' lm-mod-active'; /* */ name += ' p-mod-active'; /* */ } return name; } /** * Create the dataset for a menu bar item. * * @param data - The data to use for the item. * * @returns The dataset for the menu bar item. */ createItemDataset(data: IRenderData): ElementDataset { return data.title.dataset; } /** * Create the aria attributes for menu bar item. * * @param data - The data to use for the aria attributes. * * @returns The aria attributes object for the item. */ createItemARIA(data: IRenderData): ElementARIAAttrs { return { role: 'menuitem', 'aria-haspopup': 'true' }; } /** * Create the class name for the menu bar item icon. * * @param data - The data to use for the class name. * * @returns The full class name for the item icon. */ createIconClass(data: IRenderData): string { let name = 'lm-MenuBar-itemIcon'; /* */ name += ' p-MenuBar-itemIcon'; /* */ let extra = data.title.iconClass; return extra ? `${name} ${extra}` : name; } /** * Create the render content for the label node. * * @param data - The data to use for the label content. * * @returns The content to add to the label node. */ formatLabel(data: IRenderData): h.Child { // Fetch the label text and mnemonic index. let { label, mnemonic } = data.title; // If the index is out of range, do not modify the label. if (mnemonic < 0 || mnemonic >= label.length) { return label; } // Split the label into parts. let prefix = label.slice(0, mnemonic); let suffix = label.slice(mnemonic + 1); let char = label[mnemonic]; // Wrap the mnemonic character in a span. let span = h.span( { className: 'lm-MenuBar-itemMnemonic' + /* */ ' p-MenuBar-itemMnemonic' /* */ }, char ); // Return the content parts. return [prefix, span, suffix]; } } /** * The default `Renderer` instance. */ export const defaultRenderer = new Renderer(); } /** * The namespace for the module implementation details. */ namespace Private { /** * Create the DOM node for a menu bar. */ export function createNode(): HTMLDivElement { let node = document.createElement('div'); let content = document.createElement('ul'); content.className = 'lm-MenuBar-content'; /* */ content.classList.add('p-MenuBar-content'); /* */ node.appendChild(content); content.setAttribute('role', 'menubar'); node.tabIndex = 0; content.tabIndex = 0; return node; } /** * The results of a mnemonic search. */ export interface IMnemonicResult { /** * The index of the first matching mnemonic item, or `-1`. */ index: number; /** * Whether multiple mnemonic items matched. */ multiple: boolean; /** * The index of the first auto matched non-mnemonic item. */ auto: number; } /** * Find the best matching mnemonic item. * * The search starts at the given index and wraps around. */ export function findMnemonic( menus: ReadonlyArray, key: string, start: number ): IMnemonicResult { // Setup the result variables. let index = -1; let auto = -1; let multiple = false; // Normalize the key to upper case. let upperKey = key.toUpperCase(); // Search the items from the given start index. for (let i = 0, n = menus.length; i < n; ++i) { // Compute the wrapped index. let k = (i + start) % n; // Look up the menu title. let title = menus[k].title; // Ignore titles with an empty label. if (title.label.length === 0) { continue; } // Look up the mnemonic index for the label. let mn = title.mnemonic; // Handle a valid mnemonic index. if (mn >= 0 && mn < title.label.length) { if (title.label[mn].toUpperCase() === upperKey) { if (index === -1) { index = k; } else { multiple = true; } } continue; } // Finally, handle the auto index if possible. if (auto === -1 && title.label[0].toUpperCase() === upperKey) { auto = k; } } // Return the search results. return { index, multiple, auto }; } } lumino-2021.12.13/packages/widgets/src/panel.ts000066400000000000000000000051651415564225700211120ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { PanelLayout } from './panellayout'; import { Widget } from './widget'; /** * A simple and convenient panel widget class. * * #### Notes * This class is suitable as a base class for implementing a variety of * convenience panel widgets, but can also be used directly with CSS to * arrange a collection of widgets. * * This class provides a convenience wrapper around a [[PanelLayout]]. */ export class Panel extends Widget { /** * Construct a new panel. * * @param options - The options for initializing the panel. */ constructor(options: Panel.IOptions = {}) { super(); this.addClass('lm-Panel'); /* */ this.addClass('p-Panel'); /* */ this.layout = Private.createLayout(options); } /** * A read-only array of the widgets in the panel. */ get widgets(): ReadonlyArray { return (this.layout as PanelLayout).widgets; } /** * Add a widget to the end of the panel. * * @param widget - The widget to add to the panel. * * #### Notes * If the widget is already contained in the panel, it will be moved. */ addWidget(widget: Widget): void { (this.layout as PanelLayout).addWidget(widget); } /** * Insert a widget at the specified index. * * @param index - The index at which to insert the widget. * * @param widget - The widget to insert into to the panel. * * #### Notes * If the widget is already contained in the panel, it will be moved. */ insertWidget(index: number, widget: Widget): void { (this.layout as PanelLayout).insertWidget(index, widget); } } /** * The namespace for the `Panel` class statics. */ export namespace Panel { /** * An options object for creating a panel. */ export interface IOptions { /** * The panel layout to use for the panel. * * The default is a new `PanelLayout`. */ layout?: PanelLayout; } } /** * The namespace for the module implementation details. */ namespace Private { /** * Create a panel layout for the given panel options. */ export function createLayout(options: Panel.IOptions): PanelLayout { return options.layout || new PanelLayout(); } } lumino-2021.12.13/packages/widgets/src/panellayout.ts000066400000000000000000000227041415564225700223460ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { ArrayExt, each, IIterator, iter } from '@lumino/algorithm'; import { MessageLoop } from '@lumino/messaging'; import { Layout } from './layout'; import { Widget } from './widget'; /** * A concrete layout implementation suitable for many use cases. * * #### Notes * This class is suitable as a base class for implementing a variety of * layouts, but can also be used directly with standard CSS to layout a * collection of widgets. */ export class PanelLayout extends Layout { /** * Dispose of the resources held by the layout. * * #### Notes * This will clear and dispose all widgets in the layout. * * All reimplementations should call the superclass method. * * This method is called automatically when the parent is disposed. */ dispose(): void { while (this._widgets.length > 0) { this._widgets.pop()!.dispose(); } super.dispose(); } /** * A read-only array of the widgets in the layout. */ get widgets(): ReadonlyArray { return this._widgets; } /** * Create an iterator over the widgets in the layout. * * @returns A new iterator over the widgets in the layout. */ iter(): IIterator { return iter(this._widgets); } /** * Add a widget to the end of the layout. * * @param widget - The widget to add to the layout. * * #### Notes * If the widget is already contained in the layout, it will be moved. */ addWidget(widget: Widget): void { this.insertWidget(this._widgets.length, widget); } /** * Insert a widget into the layout at the specified index. * * @param index - The index at which to insert the widget. * * @param widget - The widget to insert into the layout. * * #### Notes * The index will be clamped to the bounds of the widgets. * * If the widget is already added to the layout, it will be moved. * * #### Undefined Behavior * An `index` which is non-integral. */ insertWidget(index: number, widget: Widget): void { // Remove the widget from its current parent. This is a no-op // if the widget's parent is already the layout parent widget. widget.parent = this.parent; // Look up the current index of the widget. let i = this._widgets.indexOf(widget); // Clamp the insert index to the array bounds. let j = Math.max(0, Math.min(index, this._widgets.length)); // If the widget is not in the array, insert it. if (i === -1) { // Insert the widget into the array. ArrayExt.insert(this._widgets, j, widget); // If the layout is parented, attach the widget to the DOM. if (this.parent) { this.attachWidget(j, widget); } // There is nothing more to do. return; } // Otherwise, the widget exists in the array and should be moved. // Adjust the index if the location is at the end of the array. if (j === this._widgets.length) { j--; } // Bail if there is no effective move. if (i === j) { return; } // Move the widget to the new location. ArrayExt.move(this._widgets, i, j); // If the layout is parented, move the widget in the DOM. if (this.parent) { this.moveWidget(i, j, widget); } } /** * Remove a widget from the layout. * * @param widget - The widget to remove from the layout. * * #### Notes * A widget is automatically removed from the layout when its `parent` * is set to `null`. This method should only be invoked directly when * removing a widget from a layout which has yet to be installed on a * parent widget. * * This method does *not* modify the widget's `parent`. */ removeWidget(widget: Widget): void { this.removeWidgetAt(this._widgets.indexOf(widget)); } /** * Remove the widget at a given index from the layout. * * @param index - The index of the widget to remove. * * #### Notes * A widget is automatically removed from the layout when its `parent` * is set to `null`. This method should only be invoked directly when * removing a widget from a layout which has yet to be installed on a * parent widget. * * This method does *not* modify the widget's `parent`. * * #### Undefined Behavior * An `index` which is non-integral. */ removeWidgetAt(index: number): void { // Remove the widget from the array. let widget = ArrayExt.removeAt(this._widgets, index); // If the layout is parented, detach the widget from the DOM. if (widget && this.parent) { this.detachWidget(index, widget); } } /** * Perform layout initialization which requires the parent widget. */ protected init(): void { super.init(); each(this, (widget, index) => { this.attachWidget(index, widget); }); } /** * Attach a widget to the parent's DOM node. * * @param index - The current index of the widget in the layout. * * @param widget - The widget to attach to the parent. * * #### Notes * This method is called automatically by the panel layout at the * appropriate time. It should not be called directly by user code. * * The default implementation adds the widgets's node to the parent's * node at the proper location, and sends the appropriate attach * messages to the widget if the parent is attached to the DOM. * * Subclasses may reimplement this method to control how the widget's * node is added to the parent's node. */ protected attachWidget(index: number, widget: Widget): void { // Look up the next sibling reference node. let ref = this.parent!.node.children[index]; // Send a `'before-attach'` message if the parent is attached. if (this.parent!.isAttached) { MessageLoop.sendMessage(widget, Widget.Msg.BeforeAttach); } // Insert the widget's node before the sibling. this.parent!.node.insertBefore(widget.node, ref); // Send an `'after-attach'` message if the parent is attached. if (this.parent!.isAttached) { MessageLoop.sendMessage(widget, Widget.Msg.AfterAttach); } } /** * Move a widget in the parent's DOM node. * * @param fromIndex - The previous index of the widget in the layout. * * @param toIndex - The current index of the widget in the layout. * * @param widget - The widget to move in the parent. * * #### Notes * This method is called automatically by the panel layout at the * appropriate time. It should not be called directly by user code. * * The default implementation moves the widget's node to the proper * location in the parent's node and sends the appropriate attach and * detach messages to the widget if the parent is attached to the DOM. * * Subclasses may reimplement this method to control how the widget's * node is moved in the parent's node. */ protected moveWidget( fromIndex: number, toIndex: number, widget: Widget ): void { // Send a `'before-detach'` message if the parent is attached. if (this.parent!.isAttached) { MessageLoop.sendMessage(widget, Widget.Msg.BeforeDetach); } // Remove the widget's node from the parent. this.parent!.node.removeChild(widget.node); // Send an `'after-detach'` and message if the parent is attached. if (this.parent!.isAttached) { MessageLoop.sendMessage(widget, Widget.Msg.AfterDetach); } // Look up the next sibling reference node. let ref = this.parent!.node.children[toIndex]; // Send a `'before-attach'` message if the parent is attached. if (this.parent!.isAttached) { MessageLoop.sendMessage(widget, Widget.Msg.BeforeAttach); } // Insert the widget's node before the sibling. this.parent!.node.insertBefore(widget.node, ref); // Send an `'after-attach'` message if the parent is attached. if (this.parent!.isAttached) { MessageLoop.sendMessage(widget, Widget.Msg.AfterAttach); } } /** * Detach a widget from the parent's DOM node. * * @param index - The previous index of the widget in the layout. * * @param widget - The widget to detach from the parent. * * #### Notes * This method is called automatically by the panel layout at the * appropriate time. It should not be called directly by user code. * * The default implementation removes the widget's node from the * parent's node, and sends the appropriate detach messages to the * widget if the parent is attached to the DOM. * * Subclasses may reimplement this method to control how the widget's * node is removed from the parent's node. */ protected detachWidget(index: number, widget: Widget): void { // Send a `'before-detach'` message if the parent is attached. if (this.parent!.isAttached) { MessageLoop.sendMessage(widget, Widget.Msg.BeforeDetach); } // Remove the widget's node from the parent. this.parent!.node.removeChild(widget.node); // Send an `'after-detach'` message if the parent is attached. if (this.parent!.isAttached) { MessageLoop.sendMessage(widget, Widget.Msg.AfterDetach); } } private _widgets: Widget[] = []; } lumino-2021.12.13/packages/widgets/src/scrollbar.ts000066400000000000000000000534261415564225700220010ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { IDisposable } from '@lumino/disposable'; import { ElementExt } from '@lumino/domutils'; import { Drag } from '@lumino/dragdrop'; import { Message } from '@lumino/messaging'; import { ISignal, Signal } from '@lumino/signaling'; import { Widget } from './widget'; /** * A widget which implements a canonical scroll bar. */ export class ScrollBar extends Widget { /** * Construct a new scroll bar. * * @param options - The options for initializing the scroll bar. */ constructor(options: ScrollBar.IOptions = {}) { super({ node: Private.createNode() }); this.addClass('lm-ScrollBar'); /* */ this.addClass('p-ScrollBar'); /* */ this.setFlag(Widget.Flag.DisallowLayout); // Set the orientation. this._orientation = options.orientation || 'vertical'; this.dataset['orientation'] = this._orientation; // Parse the rest of the options. if (options.maximum !== undefined) { this._maximum = Math.max(0, options.maximum); } if (options.page !== undefined) { this._page = Math.max(0, options.page); } if (options.value !== undefined) { this._value = Math.max(0, Math.min(options.value, this._maximum)); } } /** * A signal emitted when the user moves the scroll thumb. * * #### Notes * The payload is the current value of the scroll bar. */ get thumbMoved(): ISignal { return this._thumbMoved; } /** * A signal emitted when the user clicks a step button. * * #### Notes * The payload is whether a decrease or increase is requested. */ get stepRequested(): ISignal { return this._stepRequested; } /** * A signal emitted when the user clicks the scroll track. * * #### Notes * The payload is whether a decrease or increase is requested. */ get pageRequested(): ISignal { return this._pageRequested; } /** * Get the orientation of the scroll bar. */ get orientation(): ScrollBar.Orientation { return this._orientation; } /** * Set the orientation of the scroll bar. */ set orientation(value: ScrollBar.Orientation) { // Do nothing if the orientation does not change. if (this._orientation === value) { return; } // Release the mouse before making changes. this._releaseMouse(); // Update the internal orientation. this._orientation = value; this.dataset['orientation'] = value; // Schedule an update the scroll bar. this.update(); } /** * Get the current value of the scroll bar. */ get value(): number { return this._value; } /** * Set the current value of the scroll bar. * * #### Notes * The value will be clamped to the range `[0, maximum]`. */ set value(value: number) { // Clamp the value to the allowable range. value = Math.max(0, Math.min(value, this._maximum)); // Do nothing if the value does not change. if (this._value === value) { return; } // Update the internal value. this._value = value; // Schedule an update the scroll bar. this.update(); } /** * Get the page size of the scroll bar. * * #### Notes * The page size is the amount of visible content in the scrolled * region, expressed in data units. It determines the size of the * scroll bar thumb. */ get page(): number { return this._page; } /** * Set the page size of the scroll bar. * * #### Notes * The page size will be clamped to the range `[0, Infinity]`. */ set page(value: number) { // Clamp the page size to the allowable range. value = Math.max(0, value); // Do nothing if the value does not change. if (this._page === value) { return; } // Update the internal page size. this._page = value; // Schedule an update the scroll bar. this.update(); } /** * Get the maximum value of the scroll bar. */ get maximum(): number { return this._maximum; } /** * Set the maximum value of the scroll bar. * * #### Notes * The max size will be clamped to the range `[0, Infinity]`. */ set maximum(value: number) { // Clamp the value to the allowable range. value = Math.max(0, value); // Do nothing if the value does not change. if (this._maximum === value) { return; } // Update the internal values. this._maximum = value; // Clamp the current value to the new range. this._value = Math.min(this._value, value); // Schedule an update the scroll bar. this.update(); } /** * The scroll bar decrement button node. * * #### Notes * Modifying this node directly can lead to undefined behavior. */ get decrementNode(): HTMLDivElement { return this.node.getElementsByClassName( 'lm-ScrollBar-button' )[0] as HTMLDivElement; } /** * The scroll bar increment button node. * * #### Notes * Modifying this node directly can lead to undefined behavior. */ get incrementNode(): HTMLDivElement { return this.node.getElementsByClassName( 'lm-ScrollBar-button' )[1] as HTMLDivElement; } /** * The scroll bar track node. * * #### Notes * Modifying this node directly can lead to undefined behavior. */ get trackNode(): HTMLDivElement { return this.node.getElementsByClassName( 'lm-ScrollBar-track' )[0] as HTMLDivElement; } /** * The scroll bar thumb node. * * #### Notes * Modifying this node directly can lead to undefined behavior. */ get thumbNode(): HTMLDivElement { return this.node.getElementsByClassName( 'lm-ScrollBar-thumb' )[0] as HTMLDivElement; } /** * Handle the DOM events for the scroll bar. * * @param event - The DOM event sent to the scroll bar. * * #### Notes * This method implements the DOM `EventListener` interface and is * called in response to events on the scroll bar's DOM node. * * This should not be called directly by user code. */ handleEvent(event: Event): void { switch (event.type) { case 'mousedown': this._evtMouseDown(event as MouseEvent); break; case 'mousemove': this._evtMouseMove(event as MouseEvent); break; case 'mouseup': this._evtMouseUp(event as MouseEvent); break; case 'keydown': this._evtKeyDown(event as KeyboardEvent); break; case 'contextmenu': event.preventDefault(); event.stopPropagation(); break; } } /** * A method invoked on a 'before-attach' message. */ protected onBeforeAttach(msg: Message): void { this.node.addEventListener('mousedown', this); this.update(); } /** * A method invoked on an 'after-detach' message. */ protected onAfterDetach(msg: Message): void { this.node.removeEventListener('mousedown', this); this._releaseMouse(); } /** * A method invoked on an 'update-request' message. */ protected onUpdateRequest(msg: Message): void { // Convert the value and page into percentages. let value = (this._value * 100) / this._maximum; let page = (this._page * 100) / (this._page + this._maximum); // Clamp the value and page to the relevant range. value = Math.max(0, Math.min(value, 100)); page = Math.max(0, Math.min(page, 100)); // Fetch the thumb style. let thumbStyle = this.thumbNode.style; // Update the thumb style for the current orientation. if (this._orientation === 'horizontal') { thumbStyle.top = ''; thumbStyle.height = ''; thumbStyle.left = `${value}%`; thumbStyle.width = `${page}%`; thumbStyle.transform = `translate(${-value}%, 0%)`; } else { thumbStyle.left = ''; thumbStyle.width = ''; thumbStyle.top = `${value}%`; thumbStyle.height = `${page}%`; thumbStyle.transform = `translate(0%, ${-value}%)`; } } /** * Handle the `'keydown'` event for the scroll bar. */ private _evtKeyDown(event: KeyboardEvent): void { // Stop all input events during drag. event.preventDefault(); event.stopPropagation(); // Ignore anything except the `Escape` key. if (event.keyCode !== 27) { return; } // Fetch the previous scroll value. let value = this._pressData ? this._pressData.value : -1; // Release the mouse. this._releaseMouse(); // Restore the old scroll value if possible. if (value !== -1) { this._moveThumb(value); } } /** * Handle the `'mousedown'` event for the scroll bar. */ private _evtMouseDown(event: MouseEvent): void { // Do nothing if it's not a left mouse press. if (event.button !== 0) { return; } // Send an activate request to the scroll bar. This can be // used by message hooks to activate something relevant. this.activate(); // Do nothing if the mouse is already captured. if (this._pressData) { return; } // Find the pressed scroll bar part. let part = Private.findPart(this, event.target as HTMLElement); // Do nothing if the part is not of interest. if (!part) { return; } // Stop the event propagation. event.preventDefault(); event.stopPropagation(); // Override the mouse cursor. let override = Drag.overrideCursor('default'); // Set up the press data. this._pressData = { part, override, delta: -1, value: -1, mouseX: event.clientX, mouseY: event.clientY }; // Add the extra event listeners. document.addEventListener('mousemove', this, true); document.addEventListener('mouseup', this, true); document.addEventListener('keydown', this, true); document.addEventListener('contextmenu', this, true); // Handle a thumb press. if (part === 'thumb') { // Fetch the thumb node. let thumbNode = this.thumbNode; // Fetch the client rect for the thumb. let thumbRect = thumbNode.getBoundingClientRect(); // Update the press data delta for the current orientation. if (this._orientation === 'horizontal') { this._pressData.delta = event.clientX - thumbRect.left; } else { this._pressData.delta = event.clientY - thumbRect.top; } // Add the active class to the thumb node. thumbNode.classList.add('lm-mod-active'); /* */ thumbNode.classList.add('p-mod-active'); /* */ // Store the current value in the press data. this._pressData.value = this._value; // Finished. return; } // Handle a track press. if (part === 'track') { // Fetch the client rect for the thumb. let thumbRect = this.thumbNode.getBoundingClientRect(); // Determine the direction for the page request. let dir: 'decrement' | 'increment'; if (this._orientation === 'horizontal') { dir = event.clientX < thumbRect.left ? 'decrement' : 'increment'; } else { dir = event.clientY < thumbRect.top ? 'decrement' : 'increment'; } // Start the repeat timer. this._repeatTimer = window.setTimeout(this._onRepeat, 350); // Emit the page requested signal. this._pageRequested.emit(dir); // Finished. return; } // Handle a decrement button press. if (part === 'decrement') { // Add the active class to the decrement node. this.decrementNode.classList.add('lm-mod-active'); /* */ this.decrementNode.classList.add('p-mod-active'); /* */ // Start the repeat timer. this._repeatTimer = window.setTimeout(this._onRepeat, 350); // Emit the step requested signal. this._stepRequested.emit('decrement'); // Finished. return; } // Handle an increment button press. if (part === 'increment') { // Add the active class to the increment node. this.incrementNode.classList.add('lm-mod-active'); /* */ this.incrementNode.classList.add('p-mod-active'); /* */ // Start the repeat timer. this._repeatTimer = window.setTimeout(this._onRepeat, 350); // Emit the step requested signal. this._stepRequested.emit('increment'); // Finished. return; } } /** * Handle the `'mousemove'` event for the scroll bar. */ private _evtMouseMove(event: MouseEvent): void { // Do nothing if no drag is in progress. if (!this._pressData) { return; } // Stop the event propagation. event.preventDefault(); event.stopPropagation(); // Update the mouse position. this._pressData.mouseX = event.clientX; this._pressData.mouseY = event.clientY; // Bail if the thumb is not being dragged. if (this._pressData.part !== 'thumb') { return; } // Get the client rect for the thumb and track. let thumbRect = this.thumbNode.getBoundingClientRect(); let trackRect = this.trackNode.getBoundingClientRect(); // Fetch the scroll geometry based on the orientation. let trackPos: number; let trackSpan: number; if (this._orientation === 'horizontal') { trackPos = event.clientX - trackRect.left - this._pressData.delta; trackSpan = trackRect.width - thumbRect.width; } else { trackPos = event.clientY - trackRect.top - this._pressData.delta; trackSpan = trackRect.height - thumbRect.height; } // Compute the desired value from the scroll geometry. let value = trackSpan === 0 ? 0 : (trackPos * this._maximum) / trackSpan; // Move the thumb to the computed value. this._moveThumb(value); } /** * Handle the `'mouseup'` event for the scroll bar. */ private _evtMouseUp(event: MouseEvent): void { // Do nothing if it's not a left mouse release. if (event.button !== 0) { return; } // Stop the event propagation. event.preventDefault(); event.stopPropagation(); // Release the mouse. this._releaseMouse(); } /** * Release the mouse and restore the node states. */ private _releaseMouse(): void { // Bail if there is no press data. if (!this._pressData) { return; } // Clear the repeat timer. clearTimeout(this._repeatTimer); this._repeatTimer = -1; // Clear the press data. this._pressData.override.dispose(); this._pressData = null; // Remove the extra event listeners. document.removeEventListener('mousemove', this, true); document.removeEventListener('mouseup', this, true); document.removeEventListener('keydown', this, true); document.removeEventListener('contextmenu', this, true); // Remove the active classes from the nodes. this.thumbNode.classList.remove('lm-mod-active'); this.decrementNode.classList.remove('lm-mod-active'); this.incrementNode.classList.remove('lm-mod-active'); /* */ this.thumbNode.classList.remove('p-mod-active'); this.decrementNode.classList.remove('p-mod-active'); this.incrementNode.classList.remove('p-mod-active'); /* */ } /** * Move the thumb to the specified position. */ private _moveThumb(value: number): void { // Clamp the value to the allowed range. value = Math.max(0, Math.min(value, this._maximum)); // Bail if the value does not change. if (this._value === value) { return; } // Update the internal value. this._value = value; // Schedule an update of the scroll bar. this.update(); // Emit the thumb moved signal. this._thumbMoved.emit(value); } /** * A timeout callback for repeating the mouse press. */ private _onRepeat = () => { // Clear the repeat timer id. this._repeatTimer = -1; // Bail if the mouse has been released. if (!this._pressData) { return; } // Look up the part that was pressed. let part = this._pressData.part; // Bail if the thumb was pressed. if (part === 'thumb') { return; } // Schedule the timer for another repeat. this._repeatTimer = window.setTimeout(this._onRepeat, 20); // Get the current mouse position. let mouseX = this._pressData.mouseX; let mouseY = this._pressData.mouseY; // Handle a decrement button repeat. if (part === 'decrement') { // Bail if the mouse is not over the button. if (!ElementExt.hitTest(this.decrementNode, mouseX, mouseY)) { return; } // Emit the step requested signal. this._stepRequested.emit('decrement'); // Finished. return; } // Handle an increment button repeat. if (part === 'increment') { // Bail if the mouse is not over the button. if (!ElementExt.hitTest(this.incrementNode, mouseX, mouseY)) { return; } // Emit the step requested signal. this._stepRequested.emit('increment'); // Finished. return; } // Handle a track repeat. if (part === 'track') { // Bail if the mouse is not over the track. if (!ElementExt.hitTest(this.trackNode, mouseX, mouseY)) { return; } // Fetch the thumb node. let thumbNode = this.thumbNode; // Bail if the mouse is over the thumb. if (ElementExt.hitTest(thumbNode, mouseX, mouseY)) { return; } // Fetch the client rect for the thumb. let thumbRect = thumbNode.getBoundingClientRect(); // Determine the direction for the page request. let dir: 'decrement' | 'increment'; if (this._orientation === 'horizontal') { dir = mouseX < thumbRect.left ? 'decrement' : 'increment'; } else { dir = mouseY < thumbRect.top ? 'decrement' : 'increment'; } // Emit the page requested signal. this._pageRequested.emit(dir); // Finished. return; } }; private _value = 0; private _page = 10; private _maximum = 100; private _repeatTimer = -1; private _orientation: ScrollBar.Orientation; private _pressData: Private.IPressData | null = null; private _thumbMoved = new Signal(this); private _stepRequested = new Signal(this); private _pageRequested = new Signal(this); } /** * The namespace for the `ScrollBar` class statics. */ export namespace ScrollBar { /** * A type alias for a scroll bar orientation. */ export type Orientation = 'horizontal' | 'vertical'; /** * An options object for creating a scroll bar. */ export interface IOptions { /** * The orientation of the scroll bar. * * The default is `'vertical'`. */ orientation?: Orientation; /** * The value for the scroll bar. * * The default is `0`. */ value?: number; /** * The page size for the scroll bar. * * The default is `10`. */ page?: number; /** * The maximum value for the scroll bar. * * The default is `100`. */ maximum?: number; } } /** * The namespace for the module implementation details. */ namespace Private { /** * A type alias for the parts of a scroll bar. */ export type ScrollBarPart = 'thumb' | 'track' | 'decrement' | 'increment'; /** * An object which holds mouse press data. */ export interface IPressData { /** * The scroll bar part which was pressed. */ part: ScrollBarPart; /** * The offset of the press in thumb coordinates, or -1. */ delta: number; /** * The scroll value at the time the thumb was pressed, or -1. */ value: number; /** * The disposable which will clear the override cursor. */ override: IDisposable; /** * The current X position of the mouse. */ mouseX: number; /** * The current Y position of the mouse. */ mouseY: number; } /** * Create the DOM node for a scroll bar. */ export function createNode(): HTMLElement { let node = document.createElement('div'); let decrement = document.createElement('div'); let increment = document.createElement('div'); let track = document.createElement('div'); let thumb = document.createElement('div'); decrement.className = 'lm-ScrollBar-button'; increment.className = 'lm-ScrollBar-button'; decrement.dataset['action'] = 'decrement'; increment.dataset['action'] = 'increment'; track.className = 'lm-ScrollBar-track'; thumb.className = 'lm-ScrollBar-thumb'; /* */ decrement.classList.add('p-ScrollBar-button'); increment.classList.add('p-ScrollBar-button'); track.classList.add('p-ScrollBar-track'); thumb.classList.add('p-ScrollBar-thumb'); /* */ track.appendChild(thumb); node.appendChild(decrement); node.appendChild(track); node.appendChild(increment); return node; } /** * Find the scroll bar part which contains the given target. */ export function findPart( scrollBar: ScrollBar, target: HTMLElement ): ScrollBarPart | null { // Test the thumb. if (scrollBar.thumbNode.contains(target)) { return 'thumb'; } // Test the track. if (scrollBar.trackNode.contains(target)) { return 'track'; } // Test the decrement button. if (scrollBar.decrementNode.contains(target)) { return 'decrement'; } // Test the increment button. if (scrollBar.incrementNode.contains(target)) { return 'increment'; } // Indicate no match. return null; } } lumino-2021.12.13/packages/widgets/src/singletonlayout.ts000066400000000000000000000130461415564225700232500ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { each, empty, IIterator, once } from '@lumino/algorithm'; import { MessageLoop } from '@lumino/messaging'; import { Layout } from './layout'; import { Widget } from './widget'; /** * A concrete layout implementation which holds a single widget. * * #### Notes * This class is useful for creating simple container widgets which * hold a single child. The child should be positioned with CSS. */ export class SingletonLayout extends Layout { /** * Dispose of the resources held by the layout. */ dispose(): void { if (this._widget) { let widget = this._widget; this._widget = null; widget.dispose(); } super.dispose(); } /** * Get the child widget for the layout. */ get widget(): Widget | null { return this._widget; } /** * Set the child widget for the layout. * * #### Notes * Setting the child widget will cause the old child widget to be * automatically disposed. If that is not desired, set the parent * of the old child to `null` before assigning a new child. */ set widget(widget: Widget | null) { // Remove the widget from its current parent. This is a no-op // if the widget's parent is already the layout parent widget. if (widget) { widget.parent = this.parent; } // Bail early if the widget does not change. if (this._widget === widget) { return; } // Dispose of the old child widget. if (this._widget) { this._widget.dispose(); } // Update the internal widget. this._widget = widget; // Attach the new child widget if needed. if (this.parent && widget) { this.attachWidget(widget); } } /** * Create an iterator over the widgets in the layout. * * @returns A new iterator over the widgets in the layout. */ iter(): IIterator { return this._widget ? once(this._widget) : empty(); } /** * Remove a widget from the layout. * * @param widget - The widget to remove from the layout. * * #### Notes * A widget is automatically removed from the layout when its `parent` * is set to `null`. This method should only be invoked directly when * removing a widget from a layout which has yet to be installed on a * parent widget. * * This method does *not* modify the widget's `parent`. */ removeWidget(widget: Widget): void { // Bail early if the widget does not exist in the layout. if (this._widget !== widget) { return; } // Clear the internal widget. this._widget = null; // If the layout is parented, detach the widget from the DOM. if (this.parent) { this.detachWidget(widget); } } /** * Perform layout initialization which requires the parent widget. */ protected init(): void { super.init(); each(this, widget => { this.attachWidget(widget); }); } /** * Attach a widget to the parent's DOM node. * * @param index - The current index of the widget in the layout. * * @param widget - The widget to attach to the parent. * * #### Notes * This method is called automatically by the single layout at the * appropriate time. It should not be called directly by user code. * * The default implementation adds the widgets's node to the parent's * node at the proper location, and sends the appropriate attach * messages to the widget if the parent is attached to the DOM. * * Subclasses may reimplement this method to control how the widget's * node is added to the parent's node. */ protected attachWidget(widget: Widget): void { // Send a `'before-attach'` message if the parent is attached. if (this.parent!.isAttached) { MessageLoop.sendMessage(widget, Widget.Msg.BeforeAttach); } // Add the widget's node to the parent. this.parent!.node.appendChild(widget.node); // Send an `'after-attach'` message if the parent is attached. if (this.parent!.isAttached) { MessageLoop.sendMessage(widget, Widget.Msg.AfterAttach); } } /** * Detach a widget from the parent's DOM node. * * @param widget - The widget to detach from the parent. * * #### Notes * This method is called automatically by the single layout at the * appropriate time. It should not be called directly by user code. * * The default implementation removes the widget's node from the * parent's node, and sends the appropriate detach messages to the * widget if the parent is attached to the DOM. * * Subclasses may reimplement this method to control how the widget's * node is removed from the parent's node. */ protected detachWidget(widget: Widget): void { // Send a `'before-detach'` message if the parent is attached. if (this.parent!.isAttached) { MessageLoop.sendMessage(widget, Widget.Msg.BeforeDetach); } // Remove the widget's node from the parent. this.parent!.node.removeChild(widget.node); // Send an `'after-detach'` message if the parent is attached. if (this.parent!.isAttached) { MessageLoop.sendMessage(widget, Widget.Msg.AfterDetach); } } private _widget: Widget | null = null; } lumino-2021.12.13/packages/widgets/src/splitlayout.ts000066400000000000000000000554141415564225700224060ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { ArrayExt, each } from '@lumino/algorithm'; import { ElementExt } from '@lumino/domutils'; import { Message, MessageLoop } from '@lumino/messaging'; import { AttachedProperty } from '@lumino/properties'; import { BoxEngine, BoxSizer } from './boxengine'; import { LayoutItem } from './layout'; import { PanelLayout } from './panellayout'; import { Utils } from './utils'; import { Widget } from './widget'; /** * A layout which arranges its widgets into resizable sections. */ export class SplitLayout extends PanelLayout { /** * Construct a new split layout. * * @param options - The options for initializing the layout. */ constructor(options: SplitLayout.IOptions) { super(); this.renderer = options.renderer; if (options.orientation !== undefined) { this._orientation = options.orientation; } if (options.alignment !== undefined) { this._alignment = options.alignment; } if (options.spacing !== undefined) { this._spacing = Utils.clampDimension(options.spacing); } } /** * Dispose of the resources held by the layout. */ dispose(): void { // Dispose of the layout items. each(this._items, item => { item.dispose(); }); // Clear the layout state. this._box = null; this._items.length = 0; this._sizers.length = 0; this._handles.length = 0; // Dispose of the rest of the layout. super.dispose(); } /** * The renderer used by the split layout. */ readonly renderer: SplitLayout.IRenderer; /** * Get the layout orientation for the split layout. */ get orientation(): SplitLayout.Orientation { return this._orientation; } /** * Set the layout orientation for the split layout. */ set orientation(value: SplitLayout.Orientation) { if (this._orientation === value) { return; } this._orientation = value; if (!this.parent) { return; } this.parent.dataset['orientation'] = value; this.parent.fit(); } /** * Get the content alignment for the split layout. * * #### Notes * This is the alignment of the widgets in the layout direction. * * The alignment has no effect if the widgets can expand to fill the * entire split layout. */ get alignment(): SplitLayout.Alignment { return this._alignment; } /** * Set the content alignment for the split layout. * * #### Notes * This is the alignment of the widgets in the layout direction. * * The alignment has no effect if the widgets can expand to fill the * entire split layout. */ set alignment(value: SplitLayout.Alignment) { if (this._alignment === value) { return; } this._alignment = value; if (!this.parent) { return; } this.parent.dataset['alignment'] = value; this.parent.update(); } /** * Get the inter-element spacing for the split layout. */ get spacing(): number { return this._spacing; } /** * Set the inter-element spacing for the split layout. */ set spacing(value: number) { value = Utils.clampDimension(value); if (this._spacing === value) { return; } this._spacing = value; if (!this.parent) { return; } this.parent.fit(); } /** * A read-only array of the split handles in the layout. */ get handles(): ReadonlyArray { return this._handles; } /** * Get the relative sizes of the widgets in the layout. * * @returns A new array of the relative sizes of the widgets. * * #### Notes * The returned sizes reflect the sizes of the widgets normalized * relative to their siblings. * * This method **does not** measure the DOM nodes. */ relativeSizes(): number[] { return Private.normalize(this._sizers.map(sizer => sizer.size)); } /** * Set the relative sizes for the widgets in the layout. * * @param sizes - The relative sizes for the widgets in the panel. * * #### Notes * Extra values are ignored, too few will yield an undefined layout. * * The actual geometry of the DOM nodes is updated asynchronously. */ setRelativeSizes(sizes: number[]): void { // Copy the sizes and pad with zeros as needed. let n = this._sizers.length; let temp = sizes.slice(0, n); while (temp.length < n) { temp.push(0); } // Normalize the padded sizes. let normed = Private.normalize(temp); // Apply the normalized sizes to the sizers. for (let i = 0; i < n; ++i) { let sizer = this._sizers[i]; sizer.sizeHint = normed[i]; sizer.size = normed[i]; } // Set the flag indicating the sizes are normalized. this._hasNormedSizes = true; // Trigger an update of the parent widget. if (this.parent) { this.parent.update(); } } /** * Move the offset position of a split handle. * * @param index - The index of the handle of the interest. * * @param position - The desired offset position of the handle. * * #### Notes * The position is relative to the offset parent. * * This will move the handle as close as possible to the desired * position. The sibling widgets will be adjusted as necessary. */ moveHandle(index: number, position: number): void { // Bail if the index is invalid or the handle is hidden. let handle = this._handles[index]; if (!handle || handle.classList.contains('lm-mod-hidden')) { return; } // Compute the desired delta movement for the handle. let delta: number; if (this._orientation === 'horizontal') { delta = position - handle.offsetLeft; } else { delta = position - handle.offsetTop; } // Bail if there is no handle movement. if (delta === 0) { return; } // Prevent widget resizing unless needed. for (let sizer of this._sizers) { if (sizer.size > 0) { sizer.sizeHint = sizer.size; } } // Adjust the sizers to reflect the handle movement. BoxEngine.adjust(this._sizers, index, delta); // Update the layout of the widgets. if (this.parent) { this.parent.update(); } } /** * Perform layout initialization which requires the parent widget. */ protected init(): void { this.parent!.dataset['orientation'] = this.orientation; this.parent!.dataset['alignment'] = this.alignment; super.init(); } /** * Attach a widget to the parent's DOM node. * * @param index - The current index of the widget in the layout. * * @param widget - The widget to attach to the parent. * * #### Notes * This is a reimplementation of the superclass method. */ protected attachWidget(index: number, widget: Widget): void { // Create the item, handle, and sizer for the new widget. let item = new LayoutItem(widget); let handle = Private.createHandle(this.renderer); let average = Private.averageSize(this._sizers); let sizer = Private.createSizer(average); // Insert the item, handle, and sizer into the internal arrays. ArrayExt.insert(this._items, index, item); ArrayExt.insert(this._sizers, index, sizer); ArrayExt.insert(this._handles, index, handle); // Send a `'before-attach'` message if the parent is attached. if (this.parent!.isAttached) { MessageLoop.sendMessage(widget, Widget.Msg.BeforeAttach); } // Add the widget and handle nodes to the parent. this.parent!.node.appendChild(widget.node); this.parent!.node.appendChild(handle); // Send an `'after-attach'` message if the parent is attached. if (this.parent!.isAttached) { MessageLoop.sendMessage(widget, Widget.Msg.AfterAttach); } // Post a fit request for the parent widget. this.parent!.fit(); } /** * Move a widget in the parent's DOM node. * * @param fromIndex - The previous index of the widget in the layout. * * @param toIndex - The current index of the widget in the layout. * * @param widget - The widget to move in the parent. * * #### Notes * This is a reimplementation of the superclass method. */ protected moveWidget( fromIndex: number, toIndex: number, widget: Widget ): void { // Move the item, sizer, and handle for the widget. ArrayExt.move(this._items, fromIndex, toIndex); ArrayExt.move(this._sizers, fromIndex, toIndex); ArrayExt.move(this._handles, fromIndex, toIndex); // Post a fit request to the parent to show/hide last handle. this.parent!.fit(); } /** * Detach a widget from the parent's DOM node. * * @param index - The previous index of the widget in the layout. * * @param widget - The widget to detach from the parent. * * #### Notes * This is a reimplementation of the superclass method. */ protected detachWidget(index: number, widget: Widget): void { // Remove the item, handle, and sizer for the widget. let item = ArrayExt.removeAt(this._items, index); let handle = ArrayExt.removeAt(this._handles, index); ArrayExt.removeAt(this._sizers, index); // Send a `'before-detach'` message if the parent is attached. if (this.parent!.isAttached) { MessageLoop.sendMessage(widget, Widget.Msg.BeforeDetach); } // Remove the widget and handle nodes from the parent. this.parent!.node.removeChild(widget.node); this.parent!.node.removeChild(handle!); // Send an `'after-detach'` message if the parent is attached. if (this.parent!.isAttached) { MessageLoop.sendMessage(widget, Widget.Msg.AfterDetach); } // Dispose of the layout item. item!.dispose(); // Post a fit request for the parent widget. this.parent!.fit(); } /** * A message handler invoked on a `'before-show'` message. */ protected onBeforeShow(msg: Message): void { super.onBeforeShow(msg); this.parent!.update(); } /** * A message handler invoked on a `'before-attach'` message. */ protected onBeforeAttach(msg: Message): void { super.onBeforeAttach(msg); this.parent!.fit(); } /** * A message handler invoked on a `'child-shown'` message. */ protected onChildShown(msg: Widget.ChildMessage): void { this.parent!.fit(); } /** * A message handler invoked on a `'child-hidden'` message. */ protected onChildHidden(msg: Widget.ChildMessage): void { this.parent!.fit(); } /** * A message handler invoked on a `'resize'` message. */ protected onResize(msg: Widget.ResizeMessage): void { if (this.parent!.isVisible) { this._update(msg.width, msg.height); } } /** * A message handler invoked on an `'update-request'` message. */ protected onUpdateRequest(msg: Message): void { if (this.parent!.isVisible) { this._update(-1, -1); } } /** * A message handler invoked on a `'fit-request'` message. */ protected onFitRequest(msg: Message): void { if (this.parent!.isAttached) { this._fit(); } } /** * Update the item position. * * @param i Item index * @param isHorizontal Whether the layout is horizontal or not * @param left Left position in pixels * @param top Top position in pixels * @param height Item height * @param width Item width * @param size Item size */ protected updateItemPosition( i: number, isHorizontal: boolean, left: number, top: number, height: number, width: number, size: number ): void { const item = this._items[i]; if (item.isHidden) { return; } // Fetch the style for the handle. let handleStyle = this._handles[i].style; // Update the widget and handle, and advance the relevant edge. if (isHorizontal) { left += this.widgetOffset; item.update(left, top, size, height); left += size; handleStyle.top = `${top}px`; handleStyle.left = `${left}px`; handleStyle.width = `${this._spacing}px`; handleStyle.height = `${height}px`; } else { top += this.widgetOffset; item.update(left, top, width, size); top += size; handleStyle.top = `${top}px`; handleStyle.left = `${left}px`; handleStyle.width = `${width}px`; handleStyle.height = `${this._spacing}px`; } } /** * Fit the layout to the total size required by the widgets. */ private _fit(): void { // Update the handles and track the visible widget count. let nVisible = 0; let lastHandleIndex = -1; for (let i = 0, n = this._items.length; i < n; ++i) { if (this._items[i].isHidden) { this._handles[i].classList.add('lm-mod-hidden'); /* */ this._handles[i].classList.add('p-mod-hidden'); /* */ } else { this._handles[i].classList.remove('lm-mod-hidden'); /* */ this._handles[i].classList.remove('p-mod-hidden'); /* */ lastHandleIndex = i; nVisible++; } } // Hide the handle for the last visible widget. if (lastHandleIndex !== -1) { this._handles[lastHandleIndex].classList.add('lm-mod-hidden'); /* */ this._handles[lastHandleIndex].classList.add('p-mod-hidden'); /* */ } // Update the fixed space for the visible items. this._fixed = this._spacing * Math.max(0, nVisible - 1) + this.widgetOffset * this._items.length; // Setup the computed minimum size. let horz = this._orientation === 'horizontal'; let minW = horz ? this._fixed : 0; let minH = horz ? 0 : this._fixed; // Update the sizers and computed size limits. for (let i = 0, n = this._items.length; i < n; ++i) { // Fetch the item and corresponding box sizer. let item = this._items[i]; let sizer = this._sizers[i]; // Prevent resizing unless necessary. if (sizer.size > 0) { sizer.sizeHint = sizer.size; } // If the item is hidden, it should consume zero size. if (item.isHidden) { sizer.minSize = 0; sizer.maxSize = 0; continue; } // Update the size limits for the item. item.fit(); // Update the stretch factor. sizer.stretch = SplitLayout.getStretch(item.widget); // Update the sizer limits and computed min size. if (horz) { sizer.minSize = item.minWidth; sizer.maxSize = item.maxWidth; minW += item.minWidth; minH = Math.max(minH, item.minHeight); } else { sizer.minSize = item.minHeight; sizer.maxSize = item.maxHeight; minH += item.minHeight; minW = Math.max(minW, item.minWidth); } } // Update the box sizing and add it to the computed min size. let box = (this._box = ElementExt.boxSizing(this.parent!.node)); minW += box.horizontalSum; minH += box.verticalSum; // Update the parent's min size constraints. let style = this.parent!.node.style; style.minWidth = `${minW}px`; style.minHeight = `${minH}px`; // Set the dirty flag to ensure only a single update occurs. this._dirty = true; // Notify the ancestor that it should fit immediately. This may // cause a resize of the parent, fulfilling the required update. if (this.parent!.parent) { MessageLoop.sendMessage(this.parent!.parent!, Widget.Msg.FitRequest); } // If the dirty flag is still set, the parent was not resized. // Trigger the required update on the parent widget immediately. if (this._dirty) { MessageLoop.sendMessage(this.parent!, Widget.Msg.UpdateRequest); } } /** * Update the layout position and size of the widgets. * * The parent offset dimensions should be `-1` if unknown. */ private _update(offsetWidth: number, offsetHeight: number): void { // Clear the dirty flag to indicate the update occurred. this._dirty = false; // Compute the visible item count. let nVisible = 0; for (let i = 0, n = this._items.length; i < n; ++i) { nVisible += +!this._items[i].isHidden; } // Bail early if there are no visible items to layout. if (nVisible === 0 && this.widgetOffset === 0) { return; } // Measure the parent if the offset dimensions are unknown. if (offsetWidth < 0) { offsetWidth = this.parent!.node.offsetWidth; } if (offsetHeight < 0) { offsetHeight = this.parent!.node.offsetHeight; } // Ensure the parent box sizing data is computed. if (!this._box) { this._box = ElementExt.boxSizing(this.parent!.node); } // Compute the actual layout bounds adjusted for border and padding. let top = this._box.paddingTop; let left = this._box.paddingLeft; let width = offsetWidth - this._box.horizontalSum; let height = offsetHeight - this._box.verticalSum; // Set up the variables for justification and alignment offset. let extra = 0; let offset = 0; let horz = this._orientation === 'horizontal'; if (nVisible > 0) { // Compute the adjusted layout space. let space: number; if (horz) { // left += this.widgetOffset; space = Math.max(0, width - this._fixed); } else { // top += this.widgetOffset; space = Math.max(0, height - this._fixed); } // Scale the size hints if they are normalized. if (this._hasNormedSizes) { for (let sizer of this._sizers) { sizer.sizeHint *= space; } this._hasNormedSizes = false; } // Distribute the layout space to the box sizers. let delta = BoxEngine.calc(this._sizers, space); // Account for alignment if there is extra layout space. if (delta > 0) { switch (this._alignment) { case 'start': break; case 'center': extra = 0; offset = delta / 2; break; case 'end': extra = 0; offset = delta; break; case 'justify': extra = delta / nVisible; offset = 0; break; default: throw 'unreachable'; } } } // Layout the items using the computed box sizes. for (let i = 0, n = this._items.length; i < n; ++i) { // Fetch the item. const item = this._items[i]; // Fetch the computed size for the widget. const size = item.isHidden ? 0 : this._sizers[i].size + extra; this.updateItemPosition( i, horz, horz ? left + offset : left, horz ? top : top + offset, height, width, size ); const fullOffset = this.widgetOffset + (this._handles[i].classList.contains('lm-mod-hidden') ? 0 : this._spacing); if (horz) { left += size + fullOffset; } else { top += size + fullOffset; } } } protected widgetOffset = 0; private _fixed = 0; private _spacing = 4; private _dirty = false; private _hasNormedSizes = false; private _sizers: BoxSizer[] = []; private _items: LayoutItem[] = []; private _handles: HTMLDivElement[] = []; private _box: ElementExt.IBoxSizing | null = null; private _alignment: SplitLayout.Alignment = 'start'; private _orientation: SplitLayout.Orientation = 'horizontal'; } /** * The namespace for the `SplitLayout` class statics. */ export namespace SplitLayout { /** * A type alias for a split layout orientation. */ export type Orientation = 'horizontal' | 'vertical'; /** * A type alias for a split layout alignment. */ export type Alignment = 'start' | 'center' | 'end' | 'justify'; /** * An options object for initializing a split layout. */ export interface IOptions { /** * The renderer to use for the split layout. */ renderer: IRenderer; /** * The orientation of the layout. * * The default is `'horizontal'`. */ orientation?: Orientation; /** * The content alignment of the layout. * * The default is `'start'`. */ alignment?: Alignment; /** * The spacing between items in the layout. * * The default is `4`. */ spacing?: number; } /** * A renderer for use with a split layout. */ export interface IRenderer { /** * Create a new handle for use with a split layout. * * @returns A new handle element. */ createHandle(): HTMLDivElement; } /** * Get the split layout stretch factor for the given widget. * * @param widget - The widget of interest. * * @returns The split layout stretch factor for the widget. */ export function getStretch(widget: Widget): number { return Private.stretchProperty.get(widget); } /** * Set the split layout stretch factor for the given widget. * * @param widget - The widget of interest. * * @param value - The value for the stretch factor. */ export function setStretch(widget: Widget, value: number): void { Private.stretchProperty.set(widget, value); } } /** * The namespace for the module implementation details. */ namespace Private { /** * The property descriptor for a widget stretch factor. */ export const stretchProperty = new AttachedProperty({ name: 'stretch', create: () => 0, coerce: (owner, value) => Math.max(0, Math.floor(value)), changed: onChildSizingChanged }); /** * Create a new box sizer with the given size hint. */ export function createSizer(size: number): BoxSizer { let sizer = new BoxSizer(); sizer.sizeHint = Math.floor(size); return sizer; } /** * Create a new split handle node using the given renderer. */ export function createHandle( renderer: SplitLayout.IRenderer ): HTMLDivElement { let handle = renderer.createHandle(); handle.style.position = 'absolute'; return handle; } /** * Compute the average size of an array of box sizers. */ export function averageSize(sizers: BoxSizer[]): number { return sizers.reduce((v, s) => v + s.size, 0) / sizers.length || 0; } /** * Normalize an array of values. */ export function normalize(values: number[]): number[] { let n = values.length; if (n === 0) { return []; } let sum = values.reduce((a, b) => a + Math.abs(b), 0); return sum === 0 ? values.map(v => 1 / n) : values.map(v => v / sum); } /** * The change handler for the attached sizing properties. */ function onChildSizingChanged(child: Widget): void { if (child.parent && child.parent.layout instanceof SplitLayout) { child.parent.fit(); } } } lumino-2021.12.13/packages/widgets/src/splitpanel.ts000066400000000000000000000320201415564225700221540ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { ArrayExt } from '@lumino/algorithm'; import { IDisposable } from '@lumino/disposable'; import { Drag } from '@lumino/dragdrop'; import { Message } from '@lumino/messaging'; import { Panel } from './panel'; import { SplitLayout } from './splitlayout'; import { Widget } from './widget'; /** * A panel which arranges its widgets into resizable sections. * * #### Notes * This class provides a convenience wrapper around a [[SplitLayout]]. */ export class SplitPanel extends Panel { /** * Construct a new split panel. * * @param options - The options for initializing the split panel. */ constructor(options: SplitPanel.IOptions = {}) { super({ layout: Private.createLayout(options) }); this.addClass('lm-SplitPanel'); /* */ this.addClass('p-SplitPanel'); /* */ } /** * Dispose of the resources held by the panel. */ dispose(): void { this._releaseMouse(); super.dispose(); } /** * Get the layout orientation for the split panel. */ get orientation(): SplitPanel.Orientation { return (this.layout as SplitLayout).orientation; } /** * Set the layout orientation for the split panel. */ set orientation(value: SplitPanel.Orientation) { (this.layout as SplitLayout).orientation = value; } /** * Get the content alignment for the split panel. * * #### Notes * This is the alignment of the widgets in the layout direction. * * The alignment has no effect if the widgets can expand to fill the * entire split panel. */ get alignment(): SplitPanel.Alignment { return (this.layout as SplitLayout).alignment; } /** * Set the content alignment for the split panel. * * #### Notes * This is the alignment of the widgets in the layout direction. * * The alignment has no effect if the widgets can expand to fill the * entire split panel. */ set alignment(value: SplitPanel.Alignment) { (this.layout as SplitLayout).alignment = value; } /** * Get the inter-element spacing for the split panel. */ get spacing(): number { return (this.layout as SplitLayout).spacing; } /** * Set the inter-element spacing for the split panel. */ set spacing(value: number) { (this.layout as SplitLayout).spacing = value; } /** * The renderer used by the split panel. */ get renderer(): SplitPanel.IRenderer { return (this.layout as SplitLayout).renderer; } /** * A read-only array of the split handles in the panel. */ get handles(): ReadonlyArray { return (this.layout as SplitLayout).handles; } /** * Get the relative sizes of the widgets in the panel. * * @returns A new array of the relative sizes of the widgets. * * #### Notes * The returned sizes reflect the sizes of the widgets normalized * relative to their siblings. * * This method **does not** measure the DOM nodes. */ relativeSizes(): number[] { return (this.layout as SplitLayout).relativeSizes(); } /** * Set the relative sizes for the widgets in the panel. * * @param sizes - The relative sizes for the widgets in the panel. * * #### Notes * Extra values are ignored, too few will yield an undefined layout. * * The actual geometry of the DOM nodes is updated asynchronously. */ setRelativeSizes(sizes: number[]): void { (this.layout as SplitLayout).setRelativeSizes(sizes); } /** * Handle the DOM events for the split panel. * * @param event - The DOM event sent to the panel. * * #### Notes * This method implements the DOM `EventListener` interface and is * called in response to events on the panel's DOM node. It should * not be called directly by user code. */ handleEvent(event: Event): void { switch (event.type) { case 'mousedown': this._evtMouseDown(event as MouseEvent); break; case 'mousemove': this._evtMouseMove(event as MouseEvent); break; case 'mouseup': this._evtMouseUp(event as MouseEvent); break; case 'pointerdown': this._evtMouseDown(event as MouseEvent); break; case 'pointermove': this._evtMouseMove(event as MouseEvent); break; case 'pointerup': this._evtMouseUp(event as MouseEvent); break; case 'keydown': this._evtKeyDown(event as KeyboardEvent); break; case 'contextmenu': event.preventDefault(); event.stopPropagation(); break; } } /** * A message handler invoked on a `'before-attach'` message. */ protected onBeforeAttach(msg: Message): void { this.node.addEventListener('mousedown', this); this.node.addEventListener('pointerdown', this); } /** * A message handler invoked on an `'after-detach'` message. */ protected onAfterDetach(msg: Message): void { this.node.removeEventListener('mousedown', this); this.node.removeEventListener('pointerdown', this); this._releaseMouse(); } /** * A message handler invoked on a `'child-added'` message. */ protected onChildAdded(msg: Widget.ChildMessage): void { msg.child.addClass('lm-SplitPanel-child'); /* */ msg.child.addClass('p-SplitPanel-child'); /* */ this._releaseMouse(); } /** * A message handler invoked on a `'child-removed'` message. */ protected onChildRemoved(msg: Widget.ChildMessage): void { msg.child.removeClass('lm-SplitPanel-child'); /* */ msg.child.removeClass('p-SplitPanel-child'); /* */ this._releaseMouse(); } /** * Handle the `'keydown'` event for the split panel. */ private _evtKeyDown(event: KeyboardEvent): void { // Stop input events during drag. if (this._pressData) { event.preventDefault(); event.stopPropagation(); } // Release the mouse if `Escape` is pressed. if (event.keyCode === 27) { this._releaseMouse(); } } /** * Handle the `'mousedown'` event for the split panel. */ private _evtMouseDown(event: MouseEvent): void { // Do nothing if the left mouse button is not pressed. if (event.button !== 0) { return; } // Find the handle which contains the mouse target, if any. let layout = this.layout as SplitLayout; let index = ArrayExt.findFirstIndex(layout.handles, handle => { return handle.contains(event.target as HTMLElement); }); // Bail early if the mouse press was not on a handle. if (index === -1) { return; } // Stop the event when a split handle is pressed. event.preventDefault(); event.stopPropagation(); // Add the extra document listeners. document.addEventListener('mouseup', this, true); document.addEventListener('mousemove', this, true); document.addEventListener('pointerup', this, true); document.addEventListener('pointermove', this, true); document.addEventListener('keydown', this, true); document.addEventListener('contextmenu', this, true); // Compute the offset delta for the handle press. let delta: number; let handle = layout.handles[index]; let rect = handle.getBoundingClientRect(); if (layout.orientation === 'horizontal') { delta = event.clientX - rect.left; } else { delta = event.clientY - rect.top; } // Override the cursor and store the press data. let style = window.getComputedStyle(handle); let override = Drag.overrideCursor(style.cursor!); this._pressData = { index, delta, override }; } /** * Handle the `'mousemove'` event for the split panel. */ private _evtMouseMove(event: MouseEvent): void { // Stop the event when dragging a split handle. event.preventDefault(); event.stopPropagation(); // Compute the desired offset position for the handle. let pos: number; let layout = this.layout as SplitLayout; let rect = this.node.getBoundingClientRect(); if (layout.orientation === 'horizontal') { pos = event.clientX - rect.left - this._pressData!.delta; } else { pos = event.clientY - rect.top - this._pressData!.delta; } // Move the handle as close to the desired position as possible. layout.moveHandle(this._pressData!.index, pos); } /** * Handle the `'mouseup'` event for the split panel. */ private _evtMouseUp(event: MouseEvent): void { // Do nothing if the left mouse button is not released. if (event.button !== 0) { return; } // Stop the event when releasing a handle. event.preventDefault(); event.stopPropagation(); // Finalize the mouse release. this._releaseMouse(); } /** * Release the mouse grab for the split panel. */ private _releaseMouse(): void { // Bail early if no drag is in progress. if (!this._pressData) { return; } // Clear the override cursor. this._pressData.override.dispose(); this._pressData = null; // Remove the extra document listeners. document.removeEventListener('mouseup', this, true); document.removeEventListener('mousemove', this, true); document.removeEventListener('keydown', this, true); document.removeEventListener('pointerup', this, true); document.removeEventListener('pointermove', this, true); document.removeEventListener('contextmenu', this, true); } private _pressData: Private.IPressData | null = null; } /** * The namespace for the `SplitPanel` class statics. */ export namespace SplitPanel { /** * A type alias for a split panel orientation. */ export type Orientation = SplitLayout.Orientation; /** * A type alias for a split panel alignment. */ export type Alignment = SplitLayout.Alignment; /** * A type alias for a split panel renderer. */ export type IRenderer = SplitLayout.IRenderer; /** * An options object for initializing a split panel. */ export interface IOptions { /** * The renderer to use for the split panel. * * The default is a shared renderer instance. */ renderer?: IRenderer; /** * The layout orientation of the panel. * * The default is `'horizontal'`. */ orientation?: Orientation; /** * The content alignment of the panel. * * The default is `'start'`. */ alignment?: Alignment; /** * The spacing between items in the panel. * * The default is `4`. */ spacing?: number; /** * The split layout to use for the split panel. * * If this is provided, the other options are ignored. * * The default is a new `SplitLayout`. */ layout?: SplitLayout; } /** * The default implementation of `IRenderer`. */ export class Renderer implements IRenderer { /** * Create a new handle for use with a split panel. * * @returns A new handle element for a split panel. */ createHandle(): HTMLDivElement { let handle = document.createElement('div'); handle.className = 'lm-SplitPanel-handle'; /* */ handle.classList.add('p-SplitPanel-handle'); /* */ return handle; } } /** * The default `Renderer` instance. */ export const defaultRenderer = new Renderer(); /** * Get the split panel stretch factor for the given widget. * * @param widget - The widget of interest. * * @returns The split panel stretch factor for the widget. */ export function getStretch(widget: Widget): number { return SplitLayout.getStretch(widget); } /** * Set the split panel stretch factor for the given widget. * * @param widget - The widget of interest. * * @param value - The value for the stretch factor. */ export function setStretch(widget: Widget, value: number): void { SplitLayout.setStretch(widget, value); } } /** * The namespace for the module implementation details. */ namespace Private { /** * An object which holds mouse press data. */ export interface IPressData { /** * The index of the pressed handle. */ index: number; /** * The offset of the press in handle coordinates. */ delta: number; /** * The disposable which will clear the override cursor. */ override: IDisposable; } /** * Create a split layout for the given panel options. */ export function createLayout(options: SplitPanel.IOptions): SplitLayout { return ( options.layout || new SplitLayout({ renderer: options.renderer || SplitPanel.defaultRenderer, orientation: options.orientation, alignment: options.alignment, spacing: options.spacing }) ); } } lumino-2021.12.13/packages/widgets/src/stackedlayout.ts000066400000000000000000000247601415564225700226710ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { ArrayExt, each } from '@lumino/algorithm'; import { ElementExt } from '@lumino/domutils'; import { Message, MessageLoop } from '@lumino/messaging'; import { Layout, LayoutItem } from './layout'; import { PanelLayout } from './panellayout'; import { Widget } from './widget'; /** * A layout where visible widgets are stacked atop one another. * * #### Notes * The Z-order of the visible widgets follows their layout order. */ export class StackedLayout extends PanelLayout { constructor(options: StackedLayout.IOptions = {}) { super(options); this._hiddenMode = options.hiddenMode !== undefined ? options.hiddenMode : Widget.HiddenMode.Display; } /** * The method for hiding widgets. * * #### Notes * If there is only one child widget, `Display` hiding mode will be used * regardless of this setting. */ get hiddenMode(): Widget.HiddenMode { return this._hiddenMode; } /** * Set the method for hiding widgets. * * #### Notes * If there is only one child widget, `Display` hiding mode will be used * regardless of this setting. */ set hiddenMode(v: Widget.HiddenMode) { if (this._hiddenMode === v) { return; } this._hiddenMode = v; if (this.widgets.length > 1) { this.widgets.forEach(w => { w.hiddenMode = this._hiddenMode; }); } } /** * Dispose of the resources held by the layout. */ dispose(): void { // Dispose of the layout items. each(this._items, item => { item.dispose(); }); // Clear the layout state. this._box = null; this._items.length = 0; // Dispose of the rest of the layout. super.dispose(); } /** * Attach a widget to the parent's DOM node. * * @param index - The current index of the widget in the layout. * * @param widget - The widget to attach to the parent. * * #### Notes * This is a reimplementation of the superclass method. */ protected attachWidget(index: number, widget: Widget): void { // Using transform create an additional layer in the pixel pipeline // to limit the number of layer, it is set only if there is more than one widget. if ( this._hiddenMode === Widget.HiddenMode.Scale && this._items.length > 0 ) { if (this._items.length === 1) { this.widgets[0].hiddenMode = Widget.HiddenMode.Scale; } widget.hiddenMode = Widget.HiddenMode.Scale; } else { widget.hiddenMode = Widget.HiddenMode.Display; } // Create and add a new layout item for the widget. ArrayExt.insert(this._items, index, new LayoutItem(widget)); // Send a `'before-attach'` message if the parent is attached. if (this.parent!.isAttached) { MessageLoop.sendMessage(widget, Widget.Msg.BeforeAttach); } // Add the widget's node to the parent. this.parent!.node.appendChild(widget.node); // Send an `'after-attach'` message if the parent is attached. if (this.parent!.isAttached) { MessageLoop.sendMessage(widget, Widget.Msg.AfterAttach); } // Post a fit request for the parent widget. this.parent!.fit(); } /** * Move a widget in the parent's DOM node. * * @param fromIndex - The previous index of the widget in the layout. * * @param toIndex - The current index of the widget in the layout. * * @param widget - The widget to move in the parent. * * #### Notes * This is a reimplementation of the superclass method. */ protected moveWidget( fromIndex: number, toIndex: number, widget: Widget ): void { // Move the layout item for the widget. ArrayExt.move(this._items, fromIndex, toIndex); // Post an update request for the parent widget. this.parent!.update(); } /** * Detach a widget from the parent's DOM node. * * @param index - The previous index of the widget in the layout. * * @param widget - The widget to detach from the parent. * * #### Notes * This is a reimplementation of the superclass method. */ protected detachWidget(index: number, widget: Widget): void { // Remove the layout item for the widget. let item = ArrayExt.removeAt(this._items, index); // Send a `'before-detach'` message if the parent is attached. if (this.parent!.isAttached) { MessageLoop.sendMessage(widget, Widget.Msg.BeforeDetach); } // Remove the widget's node from the parent. this.parent!.node.removeChild(widget.node); // Send an `'after-detach'` message if the parent is attached. if (this.parent!.isAttached) { MessageLoop.sendMessage(widget, Widget.Msg.AfterDetach); } // Reset the z-index for the widget. item!.widget.node.style.zIndex = ''; // Reset the hidden mode for the widget. if (this._hiddenMode === Widget.HiddenMode.Scale) { widget.hiddenMode = Widget.HiddenMode.Display; // Reset the hidden mode for the first widget if necessary. if (this._items.length === 1) { this._items[0].widget.hiddenMode = Widget.HiddenMode.Display; } } // Dispose of the layout item. item!.dispose(); // Post a fit request for the parent widget. this.parent!.fit(); } /** * A message handler invoked on a `'before-show'` message. */ protected onBeforeShow(msg: Message): void { super.onBeforeShow(msg); this.parent!.update(); } /** * A message handler invoked on a `'before-attach'` message. */ protected onBeforeAttach(msg: Message): void { super.onBeforeAttach(msg); this.parent!.fit(); } /** * A message handler invoked on a `'child-shown'` message. */ protected onChildShown(msg: Widget.ChildMessage): void { this.parent!.fit(); } /** * A message handler invoked on a `'child-hidden'` message. */ protected onChildHidden(msg: Widget.ChildMessage): void { this.parent!.fit(); } /** * A message handler invoked on a `'resize'` message. */ protected onResize(msg: Widget.ResizeMessage): void { if (this.parent!.isVisible) { this._update(msg.width, msg.height); } } /** * A message handler invoked on an `'update-request'` message. */ protected onUpdateRequest(msg: Message): void { if (this.parent!.isVisible) { this._update(-1, -1); } } /** * A message handler invoked on a `'fit-request'` message. */ protected onFitRequest(msg: Message): void { if (this.parent!.isAttached) { this._fit(); } } /** * Fit the layout to the total size required by the widgets. */ private _fit(): void { // Set up the computed minimum size. let minW = 0; let minH = 0; // Update the computed minimum size. for (let i = 0, n = this._items.length; i < n; ++i) { // Fetch the item. let item = this._items[i]; // Ignore hidden items. if (item.isHidden) { continue; } // Update the size limits for the item. item.fit(); // Update the computed minimum size. minW = Math.max(minW, item.minWidth); minH = Math.max(minH, item.minHeight); } // Update the box sizing and add it to the computed min size. let box = (this._box = ElementExt.boxSizing(this.parent!.node)); minW += box.horizontalSum; minH += box.verticalSum; // Update the parent's min size constraints. let style = this.parent!.node.style; style.minWidth = `${minW}px`; style.minHeight = `${minH}px`; // Set the dirty flag to ensure only a single update occurs. this._dirty = true; // Notify the ancestor that it should fit immediately. This may // cause a resize of the parent, fulfilling the required update. if (this.parent!.parent) { MessageLoop.sendMessage(this.parent!.parent!, Widget.Msg.FitRequest); } // If the dirty flag is still set, the parent was not resized. // Trigger the required update on the parent widget immediately. if (this._dirty) { MessageLoop.sendMessage(this.parent!, Widget.Msg.UpdateRequest); } } /** * Update the layout position and size of the widgets. * * The parent offset dimensions should be `-1` if unknown. */ private _update(offsetWidth: number, offsetHeight: number): void { // Clear the dirty flag to indicate the update occurred. this._dirty = false; // Compute the visible item count. let nVisible = 0; for (let i = 0, n = this._items.length; i < n; ++i) { nVisible += +!this._items[i].isHidden; } // Bail early if there are no visible items to layout. if (nVisible === 0) { return; } // Measure the parent if the offset dimensions are unknown. if (offsetWidth < 0) { offsetWidth = this.parent!.node.offsetWidth; } if (offsetHeight < 0) { offsetHeight = this.parent!.node.offsetHeight; } // Ensure the parent box sizing data is computed. if (!this._box) { this._box = ElementExt.boxSizing(this.parent!.node); } // Compute the actual layout bounds adjusted for border and padding. let top = this._box.paddingTop; let left = this._box.paddingLeft; let width = offsetWidth - this._box.horizontalSum; let height = offsetHeight - this._box.verticalSum; // Update the widget stacking order and layout geometry. for (let i = 0, n = this._items.length; i < n; ++i) { // Fetch the item. let item = this._items[i]; // Ignore hidden items. if (item.isHidden) { continue; } // Set the z-index for the widget. item.widget.node.style.zIndex = `${i}`; // Update the item geometry. item.update(left, top, width, height); } } private _dirty = false; private _items: LayoutItem[] = []; private _box: ElementExt.IBoxSizing | null = null; private _hiddenMode: Widget.HiddenMode; } /** * The namespace for the `StackedLayout` class statics. */ export namespace StackedLayout { /** * An options object for initializing a stacked layout. */ export interface IOptions extends Layout.IOptions { /** * The method for hiding widgets. * * The default is `Widget.HiddenMode.Display`. */ hiddenMode?: Widget.HiddenMode; } } lumino-2021.12.13/packages/widgets/src/stackedpanel.ts000066400000000000000000000062131415564225700224440ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { ISignal, Signal } from '@lumino/signaling'; import { Panel } from './panel'; import { StackedLayout } from './stackedlayout'; import { Widget } from './widget'; /** * A panel where visible widgets are stacked atop one another. * * #### Notes * This class provides a convenience wrapper around a [[StackedLayout]]. */ export class StackedPanel extends Panel { /** * Construct a new stacked panel. * * @param options - The options for initializing the panel. */ constructor(options: StackedPanel.IOptions = {}) { super({ layout: Private.createLayout(options) }); this.addClass('lm-StackedPanel'); /* */ this.addClass('p-StackedPanel'); /* */ } /** * The method for hiding widgets. * * #### Notes * If there is only one child widget, `Display` hiding mode will be used * regardless of this setting. */ get hiddenMode(): Widget.HiddenMode { return (this.layout as StackedLayout).hiddenMode; } /** * Set the method for hiding widgets. * * #### Notes * If there is only one child widget, `Display` hiding mode will be used * regardless of this setting. */ set hiddenMode(v: Widget.HiddenMode) { (this.layout as StackedLayout).hiddenMode = v; } /** * A signal emitted when a widget is removed from a stacked panel. */ get widgetRemoved(): ISignal { return this._widgetRemoved; } /** * A message handler invoked on a `'child-added'` message. */ protected onChildAdded(msg: Widget.ChildMessage): void { msg.child.addClass('lm-StackedPanel-child'); /* */ msg.child.addClass('p-StackedPanel-child'); /* */ } /** * A message handler invoked on a `'child-removed'` message. */ protected onChildRemoved(msg: Widget.ChildMessage): void { msg.child.removeClass('lm-StackedPanel-child'); /* */ msg.child.removeClass('p-StackedPanel-child'); /* */ this._widgetRemoved.emit(msg.child); } private _widgetRemoved = new Signal(this); } /** * The namespace for the `StackedPanel` class statics. */ export namespace StackedPanel { /** * An options object for creating a stacked panel. */ export interface IOptions { /** * The stacked layout to use for the stacked panel. * * The default is a new `StackedLayout`. */ layout?: StackedLayout; } } /** * The namespace for the module implementation details. */ namespace Private { /** * Create a stacked layout for the given panel options. */ export function createLayout(options: StackedPanel.IOptions): StackedLayout { return options.layout || new StackedLayout(); } } lumino-2021.12.13/packages/widgets/src/tabbar.ts000066400000000000000000001533541415564225700212520ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { ArrayExt, each } from '@lumino/algorithm'; import { IDisposable } from '@lumino/disposable'; import { ElementExt } from '@lumino/domutils'; import { Drag } from '@lumino/dragdrop'; import { Message, MessageLoop } from '@lumino/messaging'; import { ISignal, Signal } from '@lumino/signaling'; import { ElementARIAAttrs, ElementDataset, ElementInlineStyle, h, VirtualDOM, VirtualElement } from '@lumino/virtualdom'; import { Title } from './title'; import { Widget } from './widget'; /** * A widget which displays titles as a single row or column of tabs. * * #### Notes * If CSS transforms are used to rotate nodes for vertically oriented * text, then tab dragging will not work correctly. The `tabsMovable` * property should be set to `false` when rotating nodes from CSS. */ export class TabBar extends Widget { /** * Construct a new tab bar. * * @param options - The options for initializing the tab bar. */ constructor(options: TabBar.IOptions = {}) { super({ node: Private.createNode() }); this.addClass('lm-TabBar'); /* */ this.addClass('p-TabBar'); /* */ this.contentNode.setAttribute('role', 'tablist'); this.setFlag(Widget.Flag.DisallowLayout); this.tabsMovable = options.tabsMovable || false; this.titlesEditable = options.titlesEditable || false; this.allowDeselect = options.allowDeselect || false; this.addButtonEnabled = options.addButtonEnabled || false; this.insertBehavior = options.insertBehavior || 'select-tab-if-needed'; this.name = options.name || ''; this.orientation = options.orientation || 'horizontal'; this.removeBehavior = options.removeBehavior || 'select-tab-after'; this.renderer = options.renderer || TabBar.defaultRenderer; } /** * Dispose of the resources held by the widget. */ dispose(): void { this._releaseMouse(); this._titles.length = 0; this._previousTitle = null; super.dispose(); } /** * A signal emitted when the current tab is changed. * * #### Notes * This signal is emitted when the currently selected tab is changed * either through user or programmatic interaction. * * Notably, this signal is not emitted when the index of the current * tab changes due to tabs being inserted, removed, or moved. It is * only emitted when the actual current tab node is changed. */ get currentChanged(): ISignal> { return this._currentChanged; } /** * A signal emitted when a tab is moved by the user. * * #### Notes * This signal is emitted when a tab is moved by user interaction. * * This signal is not emitted when a tab is moved programmatically. */ get tabMoved(): ISignal> { return this._tabMoved; } /** * A signal emitted when a tab is clicked by the user. * * #### Notes * If the clicked tab is not the current tab, the clicked tab will be * made current and the `currentChanged` signal will be emitted first. * * This signal is emitted even if the clicked tab is the current tab. */ get tabActivateRequested(): ISignal< this, TabBar.ITabActivateRequestedArgs > { return this._tabActivateRequested; } /** * A signal emitted when the tab bar add button is clicked. */ get addRequested(): ISignal { return this._addRequested; } /** * A signal emitted when a tab close icon is clicked. * * #### Notes * This signal is not emitted unless the tab title is `closable`. */ get tabCloseRequested(): ISignal> { return this._tabCloseRequested; } /** * A signal emitted when a tab is dragged beyond the detach threshold. * * #### Notes * This signal is emitted when the user drags a tab with the mouse, * and mouse is dragged beyond the detach threshold. * * The consumer of the signal should call `releaseMouse` and remove * the tab in order to complete the detach. * * This signal is only emitted once per drag cycle. */ get tabDetachRequested(): ISignal> { return this._tabDetachRequested; } /** * The renderer used by the tab bar. */ readonly renderer: TabBar.IRenderer; /** * Whether the tabs are movable by the user. * * #### Notes * Tabs can always be moved programmatically. */ tabsMovable: boolean; /** * Whether the titles can be user-edited. * */ get titlesEditable(): boolean { return this._titlesEditable; } /** * Set whether titles can be user edited. * */ set titlesEditable(value: boolean) { this._titlesEditable = value; } /** * Whether a tab can be deselected by the user. * * #### Notes * Tabs can be always be deselected programmatically. */ allowDeselect: boolean; /** * The selection behavior when inserting a tab. */ insertBehavior: TabBar.InsertBehavior; /** * The selection behavior when removing a tab. */ removeBehavior: TabBar.RemoveBehavior; /** * Get the currently selected title. * * #### Notes * This will be `null` if no tab is selected. */ get currentTitle(): Title | null { return this._titles[this._currentIndex] || null; } /** * Set the currently selected title. * * #### Notes * If the title does not exist, the title will be set to `null`. */ set currentTitle(value: Title | null) { this.currentIndex = value ? this._titles.indexOf(value) : -1; } /** * Get the index of the currently selected tab. * * #### Notes * This will be `-1` if no tab is selected. */ get currentIndex(): number { return this._currentIndex; } /** * Set the index of the currently selected tab. * * #### Notes * If the value is out of range, the index will be set to `-1`. */ set currentIndex(value: number) { // Adjust for an out of range index. if (value < 0 || value >= this._titles.length) { value = -1; } // Bail early if the index will not change. if (this._currentIndex === value) { return; } // Look up the previous index and title. let pi = this._currentIndex; let pt = this._titles[pi] || null; // Look up the current index and title. let ci = value; let ct = this._titles[ci] || null; // Update the current index and previous title. this._currentIndex = ci; this._previousTitle = pt; // Schedule an update of the tabs. this.update(); // Emit the current changed signal. this._currentChanged.emit({ previousIndex: pi, previousTitle: pt, currentIndex: ci, currentTitle: ct }); } /** * Get the name of the tab bar. */ get name(): string { return this._name; } /** * Set the name of the tab bar. */ set name(value: string) { this._name = value; if (value) { this.contentNode.setAttribute('aria-label', value); } else { this.contentNode.removeAttribute('aria-label'); } } /** * Get the orientation of the tab bar. * * #### Notes * This controls whether the tabs are arranged in a row or column. */ get orientation(): TabBar.Orientation { return this._orientation; } /** * Set the orientation of the tab bar. * * #### Notes * This controls whether the tabs are arranged in a row or column. */ set orientation(value: TabBar.Orientation) { // Do nothing if the orientation does not change. if (this._orientation === value) { return; } // Release the mouse before making any changes. this._releaseMouse(); // Toggle the orientation values. this._orientation = value; this.dataset['orientation'] = value; this.contentNode.setAttribute('aria-orientation', value); } /** * Whether the add button is enabled. */ get addButtonEnabled(): boolean { return this._addButtonEnabled; } /** * Set whether the add button is enabled. */ set addButtonEnabled(value: boolean) { // Do nothing if the value does not change. if (this._addButtonEnabled === value) { return; } this._addButtonEnabled = value; if (value) { this.addButtonNode.classList.remove('lm-mod-hidden'); } else { this.addButtonNode.classList.add('lm-mod-hidden'); } } /** * A read-only array of the titles in the tab bar. */ get titles(): ReadonlyArray> { return this._titles; } /** * The tab bar content node. * * #### Notes * This is the node which holds the tab nodes. * * Modifying this node directly can lead to undefined behavior. */ get contentNode(): HTMLUListElement { return this.node.getElementsByClassName( 'lm-TabBar-content' )[0] as HTMLUListElement; } /** * The tab bar add button node. * * #### Notes * This is the node which holds the add button. * * Modifying this node directly can lead to undefined behavior. */ get addButtonNode(): HTMLDivElement { return this.node.getElementsByClassName( 'lm-TabBar-addButton' )[0] as HTMLDivElement; } /** * Add a tab to the end of the tab bar. * * @param value - The title which holds the data for the tab, * or an options object to convert to a title. * * @returns The title object added to the tab bar. * * #### Notes * If the title is already added to the tab bar, it will be moved. */ addTab(value: Title | Title.IOptions): Title { return this.insertTab(this._titles.length, value); } /** * Insert a tab into the tab bar at the specified index. * * @param index - The index at which to insert the tab. * * @param value - The title which holds the data for the tab, * or an options object to convert to a title. * * @returns The title object added to the tab bar. * * #### Notes * The index will be clamped to the bounds of the tabs. * * If the title is already added to the tab bar, it will be moved. */ insertTab(index: number, value: Title | Title.IOptions): Title { // Release the mouse before making any changes. this._releaseMouse(); // Coerce the value to a title. let title = Private.asTitle(value); // Look up the index of the title. let i = this._titles.indexOf(title); // Clamp the insert index to the array bounds. let j = Math.max(0, Math.min(index, this._titles.length)); // If the title is not in the array, insert it. if (i === -1) { // Insert the title into the array. ArrayExt.insert(this._titles, j, title); // Connect to the title changed signal. title.changed.connect(this._onTitleChanged, this); // Schedule an update of the tabs. this.update(); // Adjust the current index for the insert. this._adjustCurrentForInsert(j, title); // Return the title added to the tab bar. return title; } // Otherwise, the title exists in the array and should be moved. // Adjust the index if the location is at the end of the array. if (j === this._titles.length) { j--; } // Bail if there is no effective move. if (i === j) { return title; } // Move the title to the new location. ArrayExt.move(this._titles, i, j); // Schedule an update of the tabs. this.update(); // Adjust the current index for the move. this._adjustCurrentForMove(i, j); // Return the title added to the tab bar. return title; } /** * Remove a tab from the tab bar. * * @param title - The title for the tab to remove. * * #### Notes * This is a no-op if the title is not in the tab bar. */ removeTab(title: Title): void { this.removeTabAt(this._titles.indexOf(title)); } /** * Remove the tab at a given index from the tab bar. * * @param index - The index of the tab to remove. * * #### Notes * This is a no-op if the index is out of range. */ removeTabAt(index: number): void { // Release the mouse before making any changes. this._releaseMouse(); // Remove the title from the array. let title = ArrayExt.removeAt(this._titles, index); // Bail if the index is out of range. if (!title) { return; } // Disconnect from the title changed signal. title.changed.disconnect(this._onTitleChanged, this); // Clear the previous title if it's being removed. if (title === this._previousTitle) { this._previousTitle = null; } // Schedule an update of the tabs. this.update(); // Adjust the current index for the remove. this._adjustCurrentForRemove(index, title); } /** * Remove all tabs from the tab bar. */ clearTabs(): void { // Bail if there is nothing to remove. if (this._titles.length === 0) { return; } // Release the mouse before making any changes. this._releaseMouse(); // Disconnect from the title changed signals. for (let title of this._titles) { title.changed.disconnect(this._onTitleChanged, this); } // Get the current index and title. let pi = this.currentIndex; let pt = this.currentTitle; // Reset the current index and previous title. this._currentIndex = -1; this._previousTitle = null; // Clear the title array. this._titles.length = 0; // Schedule an update of the tabs. this.update(); // If no tab was selected, there's nothing else to do. if (pi === -1) { return; } // Emit the current changed signal. this._currentChanged.emit({ previousIndex: pi, previousTitle: pt, currentIndex: -1, currentTitle: null }); } /** * Release the mouse and restore the non-dragged tab positions. * * #### Notes * This will cause the tab bar to stop handling mouse events and to * restore the tabs to their non-dragged positions. */ releaseMouse(): void { this._releaseMouse(); } /** * Handle the DOM events for the tab bar. * * @param event - The DOM event sent to the tab bar. * * #### Notes * This method implements the DOM `EventListener` interface and is * called in response to events on the tab bar's DOM node. * * This should not be called directly by user code. */ handleEvent(event: Event): void { switch (event.type) { case 'mousedown': // this._evtMouseDown(event as MouseEvent); break; case 'mousemove': // this._evtMouseMove(event as MouseEvent); break; case 'mouseup': // this._evtMouseUp(event as MouseEvent); break; case 'pointerdown': this._evtMouseDown(event as MouseEvent); break; case 'pointermove': this._evtMouseMove(event as MouseEvent); break; case 'pointerup': this._evtMouseUp(event as MouseEvent); break; case 'dblclick': this._evtDblClick(event as MouseEvent); break; case 'keydown': this._evtKeyDown(event as KeyboardEvent); break; case 'contextmenu': event.preventDefault(); event.stopPropagation(); break; } } /** * A message handler invoked on a `'before-attach'` message. */ protected onBeforeAttach(msg: Message): void { this.node.addEventListener('mousedown', this); // this.node.addEventListener('pointerdown', this); this.node.addEventListener('dblclick', this); } /** * A message handler invoked on an `'after-detach'` message. */ protected onAfterDetach(msg: Message): void { this.node.removeEventListener('mousedown', this); // this.node.removeEventListener('pointerdown', this); this.node.removeEventListener('dblclick', this); this._releaseMouse(); } /** * A message handler invoked on an `'update-request'` message. */ protected onUpdateRequest(msg: Message): void { let titles = this._titles; let renderer = this.renderer; let currentTitle = this.currentTitle; let content = new Array(titles.length); for (let i = 0, n = titles.length; i < n; ++i) { let title = titles[i]; let current = title === currentTitle; let zIndex = current ? n : n - i - 1; content[i] = renderer.renderTab({ title, current, zIndex }); } VirtualDOM.render(content, this.contentNode); } /** * Handle the `'dblclick'` event for the tab bar. */ private _evtDblClick(event: MouseEvent): void { // Do nothing if titles are not editable if (!this.titlesEditable) { return; } let tabs = this.contentNode.children; // Find the index of the released tab. let index = ArrayExt.findFirstIndex(tabs, tab => { return ElementExt.hitTest(tab, event.clientX, event.clientY); }); // Do nothing if the press is not on a tab. if (index === -1) { return; } let title = this.titles[index]; let label = tabs[index].querySelector('.lm-TabBar-tabLabel') as HTMLElement; if (label && label.contains(event.target as HTMLElement)) { let value = title.label || ''; // Clear the label element let oldValue = label.innerHTML; label.innerHTML = ''; let input = document.createElement('input'); input.classList.add('lm-TabBar-tabInput'); input.value = value; label.appendChild(input); let onblur = () => { input.removeEventListener('blur', onblur); label.innerHTML = oldValue; }; input.addEventListener('dblclick', (event: Event) => event.stopPropagation() ); input.addEventListener('blur', onblur); input.addEventListener('keydown', (event: KeyboardEvent) => { if (event.key === 'Enter') { if (input.value !== '') { title.label = title.caption = input.value; } onblur(); } else if (event.key === 'Escape') { onblur(); } }); input.select(); input.focus(); if (label.children.length > 0) { (label.children[0] as HTMLElement).focus(); } } } /** * Handle the `'keydown'` event for the tab bar. */ private _evtKeyDown(event: KeyboardEvent): void { // Stop all input events during drag. event.preventDefault(); event.stopPropagation(); // Release the mouse if `Escape` is pressed. if (event.keyCode === 27) { this._releaseMouse(); } } /** * Handle the `'mousedown'` event for the tab bar. */ private _evtMouseDown(event: MouseEvent): void { // Do nothing if it's not a left or middle mouse press. if (event.button !== 0 && event.button !== 1) { return; } // Do nothing if a drag is in progress. if (this._dragData) { return; } // Check if the add button was clicked. let addButtonClicked = this.addButtonEnabled && this.addButtonNode.contains(event.target as HTMLElement); // Lookup the tab nodes. let tabs = this.contentNode.children; // Find the index of the pressed tab. let index = ArrayExt.findFirstIndex(tabs, tab => { return ElementExt.hitTest(tab, event.clientX, event.clientY); }); // Do nothing if the press is not on a tab or the add button. if (index === -1 && !addButtonClicked) { return; } // Pressing on a tab stops the event propagation. event.preventDefault(); event.stopPropagation(); // Initialize the non-measured parts of the drag data. this._dragData = { tab: tabs[index] as HTMLElement, index: index, pressX: event.clientX, pressY: event.clientY, tabPos: -1, tabSize: -1, tabPressPos: -1, targetIndex: -1, tabLayout: null, contentRect: null, override: null, dragActive: false, dragAborted: false, detachRequested: false }; // Add the document mouse up listener. document.addEventListener('mouseup', this, true); // document.addEventListener('pointerup', this, true); // Do nothing else if the middle button or add button is clicked. if (event.button === 1 || addButtonClicked) { return; } // Do nothing else if the close icon is clicked. let icon = tabs[index].querySelector(this.renderer.closeIconSelector); if (icon && icon.contains(event.target as HTMLElement)) { return; } // Add the extra listeners if the tabs are movable. if (this.tabsMovable) { document.addEventListener('mousemove', this, true); // document.addEventListener('pointermove', this, true); document.addEventListener('keydown', this, true); document.addEventListener('contextmenu', this, true); } // Update the current index as appropriate. if (this.allowDeselect && this.currentIndex === index) { this.currentIndex = -1; } else { this.currentIndex = index; } // Do nothing else if there is no current tab. if (this.currentIndex === -1) { return; } // Emit the tab activate request signal. this._tabActivateRequested.emit({ index: this.currentIndex, title: this.currentTitle! }); } /** * Handle the `'mousemove'` event for the tab bar. */ private _evtMouseMove(event: MouseEvent): void { // Do nothing if no drag is in progress. let data = this._dragData; if (!data) { return; } // Suppress the event during a drag. event.preventDefault(); event.stopPropagation(); // Lookup the tab nodes. let tabs = this.contentNode.children; // Bail early if the drag threshold has not been met. if (!data.dragActive && !Private.dragExceeded(data, event)) { return; } // Activate the drag if necessary. if (!data.dragActive) { // Fill in the rest of the drag data measurements. let tabRect = data.tab.getBoundingClientRect(); if (this._orientation === 'horizontal') { data.tabPos = data.tab.offsetLeft; data.tabSize = tabRect.width; data.tabPressPos = data.pressX - tabRect.left; } else { data.tabPos = data.tab.offsetTop; data.tabSize = tabRect.height; data.tabPressPos = data.pressY - tabRect.top; } data.tabLayout = Private.snapTabLayout(tabs, this._orientation); data.contentRect = this.contentNode.getBoundingClientRect(); data.override = Drag.overrideCursor('default'); // Add the dragging style classes. data.tab.classList.add('lm-mod-dragging'); this.addClass('lm-mod-dragging'); /* */ data.tab.classList.add('p-mod-dragging'); this.addClass('p-mod-dragging'); /* */ // Mark the drag as active. data.dragActive = true; } // Emit the detach requested signal if the threshold is exceeded. if (!data.detachRequested && Private.detachExceeded(data, event)) { // Only emit the signal once per drag cycle. data.detachRequested = true; // Setup the arguments for the signal. let index = data.index; let clientX = event.clientX; let clientY = event.clientY; let tab = tabs[index] as HTMLElement; let title = this._titles[index]; // Emit the tab detach requested signal. this._tabDetachRequested.emit({ index, title, tab, clientX, clientY }); // Bail if the signal handler aborted the drag. if (data.dragAborted) { return; } } // Update the positions of the tabs. Private.layoutTabs(tabs, data, event, this._orientation); } /** * Handle the `'mouseup'` event for the document. */ private _evtMouseUp(event: MouseEvent): void { // Do nothing if it's not a left or middle mouse release. if (event.button !== 0 && event.button !== 1) { return; } // Do nothing if no drag is in progress. const data = this._dragData; if (!data) { return; } // Stop the event propagation. event.preventDefault(); event.stopPropagation(); // Remove the extra mouse event listeners. document.removeEventListener('mousemove', this, true); // document.removeEventListener('mouseup', this, true); // document.removeEventListener('pointermove', this, true); document.removeEventListener('pointerup', this, true); document.removeEventListener('keydown', this, true); document.removeEventListener('contextmenu', this, true); // Handle a release when the drag is not active. if (!data.dragActive) { // Clear the drag data. this._dragData = null; // Handle clicking the add button. let addButtonClicked = this.addButtonEnabled && this.addButtonNode.contains(event.target as HTMLElement); if (addButtonClicked) { this._addRequested.emit(undefined); return; } // Lookup the tab nodes. let tabs = this.contentNode.children; // Find the index of the released tab. let index = ArrayExt.findFirstIndex(tabs, tab => { return ElementExt.hitTest(tab, event.clientX, event.clientY); }); // Do nothing if the release is not on the original pressed tab. if (index !== data.index) { return; } // Ignore the release if the title is not closable. let title = this._titles[index]; if (!title.closable) { return; } // Emit the close requested signal if the middle button is released. if (event.button === 1) { this._tabCloseRequested.emit({ index, title }); return; } // Emit the close requested signal if the close icon was released. let icon = tabs[index].querySelector(this.renderer.closeIconSelector); if (icon && icon.contains(event.target as HTMLElement)) { this._tabCloseRequested.emit({ index, title }); return; } // Otherwise, there is nothing left to do. return; } // Do nothing if the left button is not released. if (event.button !== 0) { return; } // Position the tab at its final resting position. Private.finalizeTabPosition(data, this._orientation); // Remove the dragging class from the tab so it can be transitioned. data.tab.classList.remove('lm-mod-dragging'); /* */ data.tab.classList.remove('p-mod-dragging'); /* */ // Parse the transition duration for releasing the tab. let duration = Private.parseTransitionDuration(data.tab); // Complete the release on a timer to allow the tab to transition. setTimeout(() => { // Do nothing if the drag has been aborted. if (data.dragAborted) { return; } // Clear the drag data reference. this._dragData = null; // Reset the positions of the tabs. Private.resetTabPositions(this.contentNode.children, this._orientation); // Clear the cursor grab. data.override!.dispose(); // Remove the remaining dragging style. this.removeClass('lm-mod-dragging'); /* */ this.removeClass('p-mod-dragging'); /* */ // If the tab was not moved, there is nothing else to do. let i = data.index; let j = data.targetIndex; if (j === -1 || i === j) { return; } // Move the title to the new locations. ArrayExt.move(this._titles, i, j); // Adjust the current index for the move. this._adjustCurrentForMove(i, j); // Emit the tab moved signal. this._tabMoved.emit({ fromIndex: i, toIndex: j, title: this._titles[j] }); // Update the tabs immediately to prevent flicker. MessageLoop.sendMessage(this, Widget.Msg.UpdateRequest); }, duration); } /** * Release the mouse and restore the non-dragged tab positions. */ private _releaseMouse(): void { // Do nothing if no drag is in progress. let data = this._dragData; if (!data) { return; } // Clear the drag data reference. this._dragData = null; // Remove the extra mouse listeners. document.removeEventListener('mousemove', this, true); // document.removeEventListener('mouseup', this, true); // document.removeEventListener('pointermove', this, true); document.removeEventListener('pointerup', this, true); document.removeEventListener('keydown', this, true); document.removeEventListener('contextmenu', this, true); // Indicate the drag has been aborted. This allows the mouse // event handlers to return early when the drag is canceled. data.dragAborted = true; // If the drag is not active, there's nothing more to do. if (!data.dragActive) { return; } // Reset the tabs to their non-dragged positions. Private.resetTabPositions(this.contentNode.children, this._orientation); // Clear the cursor override. data.override!.dispose(); // Clear the dragging style classes. data.tab.classList.remove('lm-mod-dragging'); this.removeClass('lm-mod-dragging'); /* */ data.tab.classList.remove('p-mod-dragging'); this.removeClass('p-mod-dragging'); /* */ } /** * Adjust the current index for a tab insert operation. * * This method accounts for the tab bar's insertion behavior when * adjusting the current index and emitting the changed signal. */ private _adjustCurrentForInsert(i: number, title: Title): void { // Lookup commonly used variables. let ct = this.currentTitle; let ci = this._currentIndex; let bh = this.insertBehavior; // TODO: do we need to do an update to update the aria-selected attribute? // Handle the behavior where the new tab is always selected, // or the behavior where the new tab is selected if needed. if (bh === 'select-tab' || (bh === 'select-tab-if-needed' && ci === -1)) { this._currentIndex = i; this._previousTitle = ct; this._currentChanged.emit({ previousIndex: ci, previousTitle: ct, currentIndex: i, currentTitle: title }); return; } // Otherwise, silently adjust the current index if needed. if (ci >= i) { this._currentIndex++; } } /** * Adjust the current index for a tab move operation. * * This method will not cause the actual current tab to change. * It silently adjusts the index to account for the given move. */ private _adjustCurrentForMove(i: number, j: number): void { if (this._currentIndex === i) { this._currentIndex = j; } else if (this._currentIndex < i && this._currentIndex >= j) { this._currentIndex++; } else if (this._currentIndex > i && this._currentIndex <= j) { this._currentIndex--; } } /** * Adjust the current index for a tab remove operation. * * This method accounts for the tab bar's remove behavior when * adjusting the current index and emitting the changed signal. */ private _adjustCurrentForRemove(i: number, title: Title): void { // Lookup commonly used variables. let ci = this._currentIndex; let bh = this.removeBehavior; // Silently adjust the index if the current tab is not removed. if (ci !== i) { if (ci > i) { this._currentIndex--; } return; } // TODO: do we need to do an update to adjust the aria-selected value? // No tab gets selected if the tab bar is empty. if (this._titles.length === 0) { this._currentIndex = -1; this._currentChanged.emit({ previousIndex: i, previousTitle: title, currentIndex: -1, currentTitle: null }); return; } // Handle behavior where the next sibling tab is selected. if (bh === 'select-tab-after') { this._currentIndex = Math.min(i, this._titles.length - 1); this._currentChanged.emit({ previousIndex: i, previousTitle: title, currentIndex: this._currentIndex, currentTitle: this.currentTitle }); return; } // Handle behavior where the previous sibling tab is selected. if (bh === 'select-tab-before') { this._currentIndex = Math.max(0, i - 1); this._currentChanged.emit({ previousIndex: i, previousTitle: title, currentIndex: this._currentIndex, currentTitle: this.currentTitle }); return; } // Handle behavior where the previous history tab is selected. if (bh === 'select-previous-tab') { if (this._previousTitle) { this._currentIndex = this._titles.indexOf(this._previousTitle); this._previousTitle = null; } else { this._currentIndex = Math.min(i, this._titles.length - 1); } this._currentChanged.emit({ previousIndex: i, previousTitle: title, currentIndex: this._currentIndex, currentTitle: this.currentTitle }); return; } // Otherwise, no tab gets selected. this._currentIndex = -1; this._currentChanged.emit({ previousIndex: i, previousTitle: title, currentIndex: -1, currentTitle: null }); } /** * Handle the `changed` signal of a title object. */ private _onTitleChanged(sender: Title): void { this.update(); } private _name: string; private _currentIndex = -1; private _titles: Title[] = []; private _orientation: TabBar.Orientation; private _titlesEditable: boolean = false; private _previousTitle: Title | null = null; private _dragData: Private.IDragData | null = null; private _addButtonEnabled: boolean = false; private _tabMoved = new Signal>(this); private _currentChanged = new Signal>( this ); private _addRequested = new Signal(this); private _tabCloseRequested = new Signal< this, TabBar.ITabCloseRequestedArgs >(this); private _tabDetachRequested = new Signal< this, TabBar.ITabDetachRequestedArgs >(this); private _tabActivateRequested = new Signal< this, TabBar.ITabActivateRequestedArgs >(this); } /** * The namespace for the `TabBar` class statics. */ export namespace TabBar { /** * A type alias for a tab bar orientation. */ export type Orientation = | /** * The tabs are arranged in a single row, left-to-right. * * The tab text orientation is horizontal. */ 'horizontal' /** * The tabs are arranged in a single column, top-to-bottom. * * The tab text orientation is horizontal. */ | 'vertical'; /** * A type alias for the selection behavior on tab insert. */ export type InsertBehavior = | /** * The selected tab will not be changed. */ 'none' /** * The inserted tab will be selected. */ | 'select-tab' /** * The inserted tab will be selected if the current tab is null. */ | 'select-tab-if-needed'; /** * A type alias for the selection behavior on tab remove. */ export type RemoveBehavior = | /** * No tab will be selected. */ 'none' /** * The tab after the removed tab will be selected if possible. */ | 'select-tab-after' /** * The tab before the removed tab will be selected if possible. */ | 'select-tab-before' /** * The previously selected tab will be selected if possible. */ | 'select-previous-tab'; /** * An options object for creating a tab bar. */ export interface IOptions { /** * Name of the tab bar. * * This is used for accessibility reasons. The default is the empty string. */ name?: string; /** * The layout orientation of the tab bar. * * The default is `horizontal`. */ orientation?: TabBar.Orientation; /** * Whether the tabs are movable by the user. * * The default is `false`. */ tabsMovable?: boolean; /** * Whether a tab can be deselected by the user. * * The default is `false`. */ allowDeselect?: boolean; /** * Whether the titles can be directly edited by the user. * * The default is `false`. */ titlesEditable?: boolean; /** * Whether the add button is enabled. * * The default is `false`. */ addButtonEnabled?: boolean; /** * The selection behavior when inserting a tab. * * The default is `'select-tab-if-needed'`. */ insertBehavior?: TabBar.InsertBehavior; /** * The selection behavior when removing a tab. * * The default is `'select-tab-after'`. */ removeBehavior?: TabBar.RemoveBehavior; /** * A renderer to use with the tab bar. * * The default is a shared renderer instance. */ renderer?: IRenderer; } /** * The arguments object for the `currentChanged` signal. */ export interface ICurrentChangedArgs { /** * The previously selected index. */ readonly previousIndex: number; /** * The previously selected title. */ readonly previousTitle: Title | null; /** * The currently selected index. */ readonly currentIndex: number; /** * The currently selected title. */ readonly currentTitle: Title | null; } /** * The arguments object for the `tabMoved` signal. */ export interface ITabMovedArgs { /** * The previous index of the tab. */ readonly fromIndex: number; /** * The current index of the tab. */ readonly toIndex: number; /** * The title for the tab. */ readonly title: Title; } /** * The arguments object for the `tabActivateRequested` signal. */ export interface ITabActivateRequestedArgs { /** * The index of the tab to activate. */ readonly index: number; /** * The title for the tab. */ readonly title: Title; } /** * The arguments object for the `tabCloseRequested` signal. */ export interface ITabCloseRequestedArgs { /** * The index of the tab to close. */ readonly index: number; /** * The title for the tab. */ readonly title: Title; } /** * The arguments object for the `tabDetachRequested` signal. */ export interface ITabDetachRequestedArgs { /** * The index of the tab to detach. */ readonly index: number; /** * The title for the tab. */ readonly title: Title; /** * The node representing the tab. */ readonly tab: HTMLElement; /** * The current client X position of the mouse. */ readonly clientX: number; /** * The current client Y position of the mouse. */ readonly clientY: number; } /** * An object which holds the data to render a tab. */ export interface IRenderData { /** * The title associated with the tab. */ readonly title: Title; /** * Whether the tab is the current tab. */ readonly current: boolean; /** * The z-index for the tab. */ readonly zIndex: number; } /** * A renderer for use with a tab bar. */ export interface IRenderer { /** * A selector which matches the close icon node in a tab. */ readonly closeIconSelector: string; /** * Render the virtual element for a tab. * * @param data - The data to use for rendering the tab. * * @returns A virtual element representing the tab. */ renderTab(data: IRenderData): VirtualElement; } /** * The default implementation of `IRenderer`. * * #### Notes * Subclasses are free to reimplement rendering methods as needed. */ export class Renderer implements IRenderer { /** * A selector which matches the close icon node in a tab. */ readonly closeIconSelector = '.lm-TabBar-tabCloseIcon'; /** * Render the virtual element for a tab. * * @param data - The data to use for rendering the tab. * * @returns A virtual element representing the tab. */ renderTab(data: IRenderData): VirtualElement { let title = data.title.caption; let key = this.createTabKey(data); let id = key; let style = this.createTabStyle(data); let className = this.createTabClass(data); let dataset = this.createTabDataset(data); let aria = this.createTabARIA(data); if (data.title.closable) { return h.li( { id, key, className, title, style, dataset, ...aria }, this.renderIcon(data), this.renderLabel(data), this.renderCloseIcon(data) ); } else { return h.li( { id, key, className, title, style, dataset, ...aria }, this.renderIcon(data), this.renderLabel(data) ); } } /** * Render the icon element for a tab. * * @param data - The data to use for rendering the tab. * * @returns A virtual element representing the tab icon. */ renderIcon(data: IRenderData): VirtualElement { const { title } = data; let className = this.createIconClass(data); /* */ if (typeof title.icon === 'string') { return h.div({ className }, title.iconLabel); } /* */ // if title.icon is undefined, it will be ignored return h.div({ className }, title.icon!, title.iconLabel); } /** * Render the label element for a tab. * * @param data - The data to use for rendering the tab. * * @returns A virtual element representing the tab label. */ renderLabel(data: IRenderData): VirtualElement { return h.div( { className: 'lm-TabBar-tabLabel' + /* */ ' p-TabBar-tabLabel' /* */ }, data.title.label ); } /** * Render the close icon element for a tab. * * @param data - The data to use for rendering the tab. * * @returns A virtual element representing the tab close icon. */ renderCloseIcon(data: IRenderData): VirtualElement { return h.div({ className: 'lm-TabBar-tabCloseIcon' + /* */ ' p-TabBar-tabCloseIcon' /* */ }); } /** * Create a unique render key for the tab. * * @param data - The data to use for the tab. * * @returns The unique render key for the tab. * * #### Notes * This method caches the key against the tab title the first time * the key is generated. This enables efficient rendering of moved * tabs and avoids subtle hover style artifacts. */ createTabKey(data: IRenderData): string { let key = this._tabKeys.get(data.title); if (key === undefined) { key = `tab-key-${this._tabID++}`; this._tabKeys.set(data.title, key); } return key; } /** * Create the inline style object for a tab. * * @param data - The data to use for the tab. * * @returns The inline style data for the tab. */ createTabStyle(data: IRenderData): ElementInlineStyle { return { zIndex: `${data.zIndex}` }; } /** * Create the class name for the tab. * * @param data - The data to use for the tab. * * @returns The full class name for the tab. */ createTabClass(data: IRenderData): string { let name = 'lm-TabBar-tab'; /* */ name += ' p-TabBar-tab'; /* */ if (data.title.className) { name += ` ${data.title.className}`; } if (data.title.closable) { name += ' lm-mod-closable'; /* */ name += ' p-mod-closable'; /* */ } if (data.current) { name += ' lm-mod-current'; /* */ name += ' p-mod-current'; /* */ } return name; } /** * Create the dataset for a tab. * * @param data - The data to use for the tab. * * @returns The dataset for the tab. */ createTabDataset(data: IRenderData): ElementDataset { return data.title.dataset; } /** * Create the ARIA attributes for a tab. * * @param data - The data to use for the tab. * * @returns The ARIA attributes for the tab. */ createTabARIA(data: IRenderData): ElementARIAAttrs { return { role: 'tab', 'aria-selected': data.current.toString() }; } /** * Create the class name for the tab icon. * * @param data - The data to use for the tab. * * @returns The full class name for the tab icon. */ createIconClass(data: IRenderData): string { let name = 'lm-TabBar-tabIcon'; /* */ name += ' p-TabBar-tabIcon'; /* */ let extra = data.title.iconClass; return extra ? `${name} ${extra}` : name; } private _tabID = 0; private _tabKeys = new WeakMap, string>(); } /** * The default `Renderer` instance. */ export const defaultRenderer = new Renderer(); /** * A selector which matches the add button node in the tab bar. */ export const addButtonSelector = '.lm-TabBar-addButton'; } /** * The namespace for the module implementation details. */ namespace Private { /** * The start drag distance threshold. */ export const DRAG_THRESHOLD = 5; /** * The detach distance threshold. */ export const DETACH_THRESHOLD = 20; /** * A struct which holds the drag data for a tab bar. */ export interface IDragData { /** * The tab node being dragged. */ tab: HTMLElement; /** * The index of the tab being dragged. */ index: number; /** * The mouse press client X position. */ pressX: number; /** * The mouse press client Y position. */ pressY: number; /** * The offset left/top of the tab being dragged. * * This will be `-1` if the drag is not active. */ tabPos: number; /** * The offset width/height of the tab being dragged. * * This will be `-1` if the drag is not active. */ tabSize: number; /** * The original mouse X/Y position in tab coordinates. * * This will be `-1` if the drag is not active. */ tabPressPos: number; /** * The tab target index upon mouse release. * * This will be `-1` if the drag is not active. */ targetIndex: number; /** * The array of tab layout objects snapped at drag start. * * This will be `null` if the drag is not active. */ tabLayout: ITabLayout[] | null; /** * The bounding client rect of the tab bar content node. * * This will be `null` if the drag is not active. */ contentRect: ClientRect | null; /** * The disposable to clean up the cursor override. * * This will be `null` if the drag is not active. */ override: IDisposable | null; /** * Whether the drag is currently active. */ dragActive: boolean; /** * Whether the drag has been aborted. */ dragAborted: boolean; /** * Whether a detach request as been made. */ detachRequested: boolean; } /** * An object which holds layout data for a tab. */ export interface ITabLayout { /** * The left/top margin value for the tab. */ margin: number; /** * The offset left/top position of the tab. */ pos: number; /** * The offset width/height of the tab. */ size: number; } /** * Create the DOM node for a tab bar. */ export function createNode(): HTMLDivElement { let node = document.createElement('div'); let content = document.createElement('ul'); content.setAttribute('role', 'tablist'); content.className = 'lm-TabBar-content'; /* */ content.classList.add('p-TabBar-content'); /* */ node.appendChild(content); let add = document.createElement('div'); add.className = 'lm-TabBar-addButton lm-mod-hidden'; node.appendChild(add); return node; } /** * Coerce a title or options into a real title. */ export function asTitle(value: Title | Title.IOptions): Title { return value instanceof Title ? value : new Title(value); } /** * Parse the transition duration for a tab node. */ export function parseTransitionDuration(tab: HTMLElement): number { let style = window.getComputedStyle(tab); return 1000 * (parseFloat(style.transitionDuration!) || 0); } /** * Get a snapshot of the current tab layout values. */ export function snapTabLayout( tabs: HTMLCollection, orientation: TabBar.Orientation ): ITabLayout[] { let layout = new Array(tabs.length); for (let i = 0, n = tabs.length; i < n; ++i) { let node = tabs[i] as HTMLElement; let style = window.getComputedStyle(node); if (orientation === 'horizontal') { layout[i] = { pos: node.offsetLeft, size: node.offsetWidth, margin: parseFloat(style.marginLeft!) || 0 }; } else { layout[i] = { pos: node.offsetTop, size: node.offsetHeight, margin: parseFloat(style.marginTop!) || 0 }; } } return layout; } /** * Test if the event exceeds the drag threshold. */ export function dragExceeded(data: IDragData, event: MouseEvent): boolean { let dx = Math.abs(event.clientX - data.pressX); let dy = Math.abs(event.clientY - data.pressY); return dx >= DRAG_THRESHOLD || dy >= DRAG_THRESHOLD; } /** * Test if the event exceeds the drag detach threshold. */ export function detachExceeded(data: IDragData, event: MouseEvent): boolean { let rect = data.contentRect!; return ( event.clientX < rect.left - DETACH_THRESHOLD || event.clientX >= rect.right + DETACH_THRESHOLD || event.clientY < rect.top - DETACH_THRESHOLD || event.clientY >= rect.bottom + DETACH_THRESHOLD ); } /** * Update the relative tab positions and computed target index. */ export function layoutTabs( tabs: HTMLCollection, data: IDragData, event: MouseEvent, orientation: TabBar.Orientation ): void { // Compute the orientation-sensitive values. let pressPos: number; let localPos: number; let clientPos: number; let clientSize: number; if (orientation === 'horizontal') { pressPos = data.pressX; localPos = event.clientX - data.contentRect!.left; clientPos = event.clientX; clientSize = data.contentRect!.width; } else { pressPos = data.pressY; localPos = event.clientY - data.contentRect!.top; clientPos = event.clientY; clientSize = data.contentRect!.height; } // Compute the target data. let targetIndex = data.index; let targetPos = localPos - data.tabPressPos; let targetEnd = targetPos + data.tabSize; // Update the relative tab positions. for (let i = 0, n = tabs.length; i < n; ++i) { let pxPos: string; let layout = data.tabLayout![i]; let threshold = layout.pos + (layout.size >> 1); if (i < data.index && targetPos < threshold) { pxPos = `${data.tabSize + data.tabLayout![i + 1].margin}px`; targetIndex = Math.min(targetIndex, i); } else if (i > data.index && targetEnd > threshold) { pxPos = `${-data.tabSize - layout.margin}px`; targetIndex = Math.max(targetIndex, i); } else if (i === data.index) { let ideal = clientPos - pressPos; let limit = clientSize - (data.tabPos + data.tabSize); pxPos = `${Math.max(-data.tabPos, Math.min(ideal, limit))}px`; } else { pxPos = ''; } if (orientation === 'horizontal') { (tabs[i] as HTMLElement).style.left = pxPos; } else { (tabs[i] as HTMLElement).style.top = pxPos; } } // Update the computed target index. data.targetIndex = targetIndex; } /** * Position the drag tab at its final resting relative position. */ export function finalizeTabPosition( data: IDragData, orientation: TabBar.Orientation ): void { // Compute the orientation-sensitive client size. let clientSize: number; if (orientation === 'horizontal') { clientSize = data.contentRect!.width; } else { clientSize = data.contentRect!.height; } // Compute the ideal final tab position. let ideal: number; if (data.targetIndex === data.index) { ideal = 0; } else if (data.targetIndex > data.index) { let tgt = data.tabLayout![data.targetIndex]; ideal = tgt.pos + tgt.size - data.tabSize - data.tabPos; } else { let tgt = data.tabLayout![data.targetIndex]; ideal = tgt.pos - data.tabPos; } // Compute the tab position limit. let limit = clientSize - (data.tabPos + data.tabSize); let final = Math.max(-data.tabPos, Math.min(ideal, limit)); // Set the final orientation-sensitive position. if (orientation === 'horizontal') { data.tab.style.left = `${final}px`; } else { data.tab.style.top = `${final}px`; } } /** * Reset the relative positions of the given tabs. */ export function resetTabPositions( tabs: HTMLCollection, orientation: TabBar.Orientation ): void { each(tabs, tab => { if (orientation === 'horizontal') { (tab as HTMLElement).style.left = ''; } else { (tab as HTMLElement).style.top = ''; } }); } } lumino-2021.12.13/packages/widgets/src/tabpanel.ts000066400000000000000000000323311415564225700215740ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { Platform } from '@lumino/domutils'; import { MessageLoop } from '@lumino/messaging'; import { ISignal, Signal } from '@lumino/signaling'; import { BoxLayout } from './boxlayout'; import { StackedPanel } from './stackedpanel'; import { TabBar } from './tabbar'; import { Widget } from './widget'; /** * A widget which combines a `TabBar` and a `StackedPanel`. * * #### Notes * This is a simple panel which handles the common case of a tab bar * placed next to a content area. The selected tab controls the widget * which is shown in the content area. * * For use cases which require more control than is provided by this * panel, the `TabBar` widget may be used independently. */ export class TabPanel extends Widget { /** * Construct a new tab panel. * * @param options - The options for initializing the tab panel. */ constructor(options: TabPanel.IOptions = {}) { super(); this.addClass('lm-TabPanel'); /* */ this.addClass('p-TabPanel'); /* */ // Create the tab bar and stacked panel. this.tabBar = new TabBar(options); this.tabBar.addClass('lm-TabPanel-tabBar'); this.stackedPanel = new StackedPanel(); this.stackedPanel.addClass('lm-TabPanel-stackedPanel'); /* */ this.tabBar.addClass('p-TabPanel-tabBar'); this.stackedPanel.addClass('p-TabPanel-stackedPanel'); /* */ // Connect the tab bar signal handlers. this.tabBar.tabMoved.connect(this._onTabMoved, this); this.tabBar.currentChanged.connect(this._onCurrentChanged, this); this.tabBar.tabCloseRequested.connect(this._onTabCloseRequested, this); this.tabBar.tabActivateRequested.connect( this._onTabActivateRequested, this ); this.tabBar.addRequested.connect(this._onTabAddRequested, this); // Connect the stacked panel signal handlers. this.stackedPanel.widgetRemoved.connect(this._onWidgetRemoved, this); // Get the data related to the placement. this._tabPlacement = options.tabPlacement || 'top'; let direction = Private.directionFromPlacement(this._tabPlacement); let orientation = Private.orientationFromPlacement(this._tabPlacement); // Configure the tab bar for the placement. this.tabBar.orientation = orientation; this.tabBar.dataset['placement'] = this._tabPlacement; // Create the box layout. let layout = new BoxLayout({ direction, spacing: 0 }); // Set the stretch factors for the child widgets. BoxLayout.setStretch(this.tabBar, 0); BoxLayout.setStretch(this.stackedPanel, 1); // Add the child widgets to the layout. layout.addWidget(this.tabBar); layout.addWidget(this.stackedPanel); // Install the layout on the tab panel. this.layout = layout; } /** * A signal emitted when the current tab is changed. * * #### Notes * This signal is emitted when the currently selected tab is changed * either through user or programmatic interaction. * * Notably, this signal is not emitted when the index of the current * tab changes due to tabs being inserted, removed, or moved. It is * only emitted when the actual current tab node is changed. */ get currentChanged(): ISignal { return this._currentChanged; } /** * Get the index of the currently selected tab. * * #### Notes * This will be `-1` if no tab is selected. */ get currentIndex(): number { return this.tabBar.currentIndex; } /** * Set the index of the currently selected tab. * * #### Notes * If the index is out of range, it will be set to `-1`. */ set currentIndex(value: number) { this.tabBar.currentIndex = value; } /** * Get the currently selected widget. * * #### Notes * This will be `null` if there is no selected tab. */ get currentWidget(): Widget | null { let title = this.tabBar.currentTitle; return title ? title.owner : null; } /** * Set the currently selected widget. * * #### Notes * If the widget is not in the panel, it will be set to `null`. */ set currentWidget(value: Widget | null) { this.tabBar.currentTitle = value ? value.title : null; } /** * Get the whether the tabs are movable by the user. * * #### Notes * Tabs can always be moved programmatically. */ get tabsMovable(): boolean { return this.tabBar.tabsMovable; } /** * Set the whether the tabs are movable by the user. * * #### Notes * Tabs can always be moved programmatically. */ set tabsMovable(value: boolean) { this.tabBar.tabsMovable = value; } /** * Get the whether the add button is enabled. * */ get addButtonEnabled(): boolean { return this.tabBar.addButtonEnabled; } /** * Set the whether the add button is enabled. * */ set addButtonEnabled(value: boolean) { this.tabBar.addButtonEnabled = value; } /** * Get the tab placement for the tab panel. * * #### Notes * This controls the position of the tab bar relative to the content. */ get tabPlacement(): TabPanel.TabPlacement { return this._tabPlacement; } /** * Set the tab placement for the tab panel. * * #### Notes * This controls the position of the tab bar relative to the content. */ set tabPlacement(value: TabPanel.TabPlacement) { // Bail if the placement does not change. if (this._tabPlacement === value) { return; } // Update the internal value. this._tabPlacement = value; // Get the values related to the placement. let direction = Private.directionFromPlacement(value); let orientation = Private.orientationFromPlacement(value); // Configure the tab bar for the placement. this.tabBar.orientation = orientation; this.tabBar.dataset['placement'] = value; // Update the layout direction. (this.layout as BoxLayout).direction = direction; } /** * A signal emitted when the add button on a tab bar is clicked. * */ get addRequested(): ISignal> { return this._addRequested; } /** * The tab bar used by the tab panel. * * #### Notes * Modifying the tab bar directly can lead to undefined behavior. */ readonly tabBar: TabBar; /** * The stacked panel used by the tab panel. * * #### Notes * Modifying the panel directly can lead to undefined behavior. */ readonly stackedPanel: StackedPanel; /** * A read-only array of the widgets in the panel. */ get widgets(): ReadonlyArray { return this.stackedPanel.widgets; } /** * Add a widget to the end of the tab panel. * * @param widget - The widget to add to the tab panel. * * #### Notes * If the widget is already contained in the panel, it will be moved. * * The widget's `title` is used to populate the tab. */ addWidget(widget: Widget): void { this.insertWidget(this.widgets.length, widget); } /** * Insert a widget into the tab panel at a specified index. * * @param index - The index at which to insert the widget. * * @param widget - The widget to insert into to the tab panel. * * #### Notes * If the widget is already contained in the panel, it will be moved. * * The widget's `title` is used to populate the tab. */ insertWidget(index: number, widget: Widget): void { if (widget !== this.currentWidget) { widget.hide(); } this.stackedPanel.insertWidget(index, widget); this.tabBar.insertTab(index, widget.title); widget.node.setAttribute('role', 'tabpanel'); let renderer = this.tabBar.renderer; if (renderer instanceof TabBar.Renderer) { let tabId = renderer.createTabKey({ title: widget.title, current: false, zIndex: 0 }); widget.node.setAttribute('aria-labelledby', tabId); } } /** * Handle the `currentChanged` signal from the tab bar. */ private _onCurrentChanged( sender: TabBar, args: TabBar.ICurrentChangedArgs ): void { // Extract the previous and current title from the args. let { previousIndex, previousTitle, currentIndex, currentTitle } = args; // Extract the widgets from the titles. let previousWidget = previousTitle ? previousTitle.owner : null; let currentWidget = currentTitle ? currentTitle.owner : null; // Hide the previous widget. if (previousWidget) { previousWidget.hide(); } // Show the current widget. if (currentWidget) { currentWidget.show(); } // Emit the `currentChanged` signal for the tab panel. this._currentChanged.emit({ previousIndex, previousWidget, currentIndex, currentWidget }); // Flush the message loop on IE and Edge to prevent flicker. if (Platform.IS_EDGE || Platform.IS_IE) { MessageLoop.flush(); } } /** * Handle the `tabAddRequested` signal from the tab bar. */ private _onTabAddRequested(sender: TabBar, args: void): void { this._addRequested.emit(sender); } /** * Handle the `tabActivateRequested` signal from the tab bar. */ private _onTabActivateRequested( sender: TabBar, args: TabBar.ITabActivateRequestedArgs ): void { args.title.owner.activate(); } /** * Handle the `tabCloseRequested` signal from the tab bar. */ private _onTabCloseRequested( sender: TabBar, args: TabBar.ITabCloseRequestedArgs ): void { args.title.owner.close(); } /** * Handle the `tabMoved` signal from the tab bar. */ private _onTabMoved( sender: TabBar, args: TabBar.ITabMovedArgs ): void { this.stackedPanel.insertWidget(args.toIndex, args.title.owner); } /** * Handle the `widgetRemoved` signal from the stacked panel. */ private _onWidgetRemoved(sender: StackedPanel, widget: Widget): void { widget.node.removeAttribute('role'); widget.node.removeAttribute('aria-labelledby'); this.tabBar.removeTab(widget.title); } private _tabPlacement: TabPanel.TabPlacement; private _currentChanged = new Signal( this ); private _addRequested = new Signal>(this); } /** * The namespace for the `TabPanel` class statics. */ export namespace TabPanel { /** * A type alias for tab placement in a tab bar. */ export type TabPlacement = | /** * The tabs are placed as a row above the content. */ 'top' /** * The tabs are placed as a column to the left of the content. */ | 'left' /** * The tabs are placed as a column to the right of the content. */ | 'right' /** * The tabs are placed as a row below the content. */ | 'bottom'; /** * An options object for initializing a tab panel. */ export interface IOptions { /** * Whether the tabs are movable by the user. * * The default is `false`. */ tabsMovable?: boolean; /** * Whether the button to add new tabs is enabled. * * The default is `false`. */ addButtonEnabled?: boolean; /** * The placement of the tab bar relative to the content. * * The default is `'top'`. */ tabPlacement?: TabPlacement; /** * The renderer for the panel's tab bar. * * The default is a shared renderer instance. */ renderer?: TabBar.IRenderer; } /** * The arguments object for the `currentChanged` signal. */ export interface ICurrentChangedArgs { /** * The previously selected index. */ previousIndex: number; /** * The previously selected widget. */ previousWidget: Widget | null; /** * The currently selected index. */ currentIndex: number; /** * The currently selected widget. */ currentWidget: Widget | null; } } /** * The namespace for the module implementation details. */ namespace Private { /** * Convert a tab placement to tab bar orientation. */ export function orientationFromPlacement( plc: TabPanel.TabPlacement ): TabBar.Orientation { return placementToOrientationMap[plc]; } /** * Convert a tab placement to a box layout direction. */ export function directionFromPlacement( plc: TabPanel.TabPlacement ): BoxLayout.Direction { return placementToDirectionMap[plc]; } /** * A mapping of tab placement to tab bar orientation. */ const placementToOrientationMap: { [key: string]: TabBar.Orientation } = { top: 'horizontal', left: 'vertical', right: 'vertical', bottom: 'horizontal' }; /** * A mapping of tab placement to box layout direction. */ const placementToDirectionMap: { [key: string]: BoxLayout.Direction } = { top: 'top-to-bottom', left: 'left-to-right', right: 'right-to-left', bottom: 'bottom-to-top' }; } lumino-2021.12.13/packages/widgets/src/title.ts000066400000000000000000000224531415564225700211330ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { ISignal, Signal } from '@lumino/signaling'; import { VirtualElement } from '@lumino/virtualdom'; /** * An object which holds data related to an object's title. * * #### Notes * A title object is intended to hold the data necessary to display a * header for a particular object. A common example is the `TabPanel`, * which uses the widget title to populate the tab for a child widget. */ export class Title { /** * Construct a new title. * * @param options - The options for initializing the title. */ constructor(options: Title.IOptions) { this.owner = options.owner; if (options.label !== undefined) { this._label = options.label; } if (options.mnemonic !== undefined) { this._mnemonic = options.mnemonic; } if (options.icon !== undefined) { /* */ if (typeof options.icon === 'string') { // when ._icon is null, the .icon getter will alias .iconClass this._icon = null; this._iconClass = options.icon; } else { /* */ this._icon = options.icon; /* */ } /* */ } else { /* */ // if unset, default to aliasing .iconClass this._icon = null; } /* */ if (options.iconClass !== undefined) { this._iconClass = options.iconClass; } if (options.iconLabel !== undefined) { this._iconLabel = options.iconLabel; } if (options.iconRenderer !== undefined) { this._icon = options.iconRenderer; } if (options.caption !== undefined) { this._caption = options.caption; } if (options.className !== undefined) { this._className = options.className; } if (options.closable !== undefined) { this._closable = options.closable; } this._dataset = options.dataset || {}; } /** * A signal emitted when the state of the title changes. */ get changed(): ISignal { return this._changed; } /** * The object which owns the title. */ readonly owner: T; /** * Get the label for the title. * * #### Notes * The default value is an empty string. */ get label(): string { return this._label; } /** * Set the label for the title. */ set label(value: string) { if (this._label === value) { return; } this._label = value; this._changed.emit(undefined); } /** * Get the mnemonic index for the title. * * #### Notes * The default value is `-1`. */ get mnemonic(): number { return this._mnemonic; } /** * Set the mnemonic index for the title. */ set mnemonic(value: number) { if (this._mnemonic === value) { return; } this._mnemonic = value; this._changed.emit(undefined); } /** * Get the icon renderer for the title. * * #### Notes * The default value is undefined. * * DEPRECATED: if set to a string value, the .icon field will function as * an alias for the .iconClass field, for backwards compatibility */ get icon(): | VirtualElement.IRenderer | undefined /* */ | string /* */ { /* */ if (this._icon === null) { // only alias .iconClass if ._icon has been explicitly nulled return this.iconClass; } /* */ return this._icon; } /** * Set the icon renderer for the title. * * #### Notes * A renderer is an object that supplies a render and unrender function. * * DEPRECATED: if set to a string value, the .icon field will function as * an alias for the .iconClass field, for backwards compatibility */ set icon( value: | VirtualElement.IRenderer | undefined /* */ | string /* */ ) { /* */ if (typeof value === 'string') { // when ._icon is null, the .icon getter will alias .iconClass this._icon = null; this.iconClass = value; } else { /* */ if (this._icon === value) { return; } this._icon = value; this._changed.emit(undefined); /* */ } /* */ } /** * Get the icon class name for the title. * * #### Notes * The default value is an empty string. */ get iconClass(): string { return this._iconClass; } /** * Set the icon class name for the title. * * #### Notes * Multiple class names can be separated with whitespace. */ set iconClass(value: string) { if (this._iconClass === value) { return; } this._iconClass = value; this._changed.emit(undefined); } /** * Get the icon label for the title. * * #### Notes * The default value is an empty string. */ get iconLabel(): string { return this._iconLabel; } /** * Set the icon label for the title. * * #### Notes * Multiple class names can be separated with whitespace. */ set iconLabel(value: string) { if (this._iconLabel === value) { return; } this._iconLabel = value; this._changed.emit(undefined); } /** * @deprecated Use `icon` instead. */ get iconRenderer(): VirtualElement.IRenderer | undefined { return this._icon || undefined; } /** * @deprecated Use `icon` instead. */ set iconRenderer(value: VirtualElement.IRenderer | undefined) { this.icon = value; } /** * Get the caption for the title. * * #### Notes * The default value is an empty string. */ get caption(): string { return this._caption; } /** * Set the caption for the title. */ set caption(value: string) { if (this._caption === value) { return; } this._caption = value; this._changed.emit(undefined); } /** * Get the extra class name for the title. * * #### Notes * The default value is an empty string. */ get className(): string { return this._className; } /** * Set the extra class name for the title. * * #### Notes * Multiple class names can be separated with whitespace. */ set className(value: string) { if (this._className === value) { return; } this._className = value; this._changed.emit(undefined); } /** * Get the closable state for the title. * * #### Notes * The default value is `false`. */ get closable(): boolean { return this._closable; } /** * Set the closable state for the title. * * #### Notes * This controls the presence of a close icon when applicable. */ set closable(value: boolean) { if (this._closable === value) { return; } this._closable = value; this._changed.emit(undefined); } /** * Get the dataset for the title. * * #### Notes * The default value is an empty dataset. */ get dataset(): Title.Dataset { return this._dataset; } /** * Set the dataset for the title. * * #### Notes * This controls the data attributes when applicable. */ set dataset(value: Title.Dataset) { if (this._dataset === value) { return; } this._dataset = value; this._changed.emit(undefined); } private _label = ''; private _caption = ''; private _mnemonic = -1; private _icon: | VirtualElement.IRenderer | undefined /* */ | null /* */; private _iconClass = ''; private _iconLabel = ''; private _className = ''; private _closable = false; private _dataset: Title.Dataset; private _changed = new Signal(this); } /** * The namespace for the `Title` class statics. */ export namespace Title { /** * A type alias for a simple immutable string dataset. */ export type Dataset = { readonly [key: string]: string }; /** * An options object for initializing a title. */ export interface IOptions { /** * The object which owns the title. */ owner: T; /** * The label for the title. */ label?: string; /** * The mnemonic index for the title. */ mnemonic?: number; /** * The icon renderer for the title. * * DEPRECATED: if set to a string value, the .icon field will function as * an alias for the .iconClass field, for backwards compatibility */ icon?: VirtualElement.IRenderer | string; /** * The icon class name for the title. */ iconClass?: string; /** * The icon label for the title. */ iconLabel?: string; /** * @deprecated Use `icon` instead. */ iconRenderer?: VirtualElement.IRenderer; /** * The caption for the title. */ caption?: string; /** * The extra class name for the title. */ className?: string; /** * The closable state for the title. */ closable?: boolean; /** * The dataset for the title. */ dataset?: Dataset; } } lumino-2021.12.13/packages/widgets/src/utils.ts000066400000000000000000000003301415564225700211400ustar00rootroot00000000000000export namespace Utils { /** * Clamp a dimension value to an integer >= 0. */ export function clampDimension(value: number): number { return Math.max(0, Math.floor(value)); } } export default Utils; lumino-2021.12.13/packages/widgets/src/widget.ts000066400000000000000000000723201415564225700212730ustar00rootroot00000000000000/* eslint-disable @typescript-eslint/no-empty-function */ // Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { empty, IIterator } from '@lumino/algorithm'; import { IObservableDisposable } from '@lumino/disposable'; import { ConflatableMessage, IMessageHandler, Message, MessageLoop } from '@lumino/messaging'; import { AttachedProperty } from '@lumino/properties'; import { ISignal, Signal } from '@lumino/signaling'; import { Layout } from './layout'; import { Title } from './title'; /** * The base class of the lumino widget hierarchy. * * #### Notes * This class will typically be subclassed in order to create a useful * widget. However, it can be used directly to host externally created * content. */ export class Widget implements IMessageHandler, IObservableDisposable { /** * Construct a new widget. * * @param options - The options for initializing the widget. */ constructor(options: Widget.IOptions = {}) { this.node = Private.createNode(options); this.addClass('lm-Widget'); /* */ this.addClass('p-Widget'); /* */ } /** * Dispose of the widget and its descendant widgets. * * #### Notes * It is unsafe to use the widget after it has been disposed. * * All calls made to this method after the first are a no-op. */ dispose(): void { // Do nothing if the widget is already disposed. if (this.isDisposed) { return; } // Set the disposed flag and emit the disposed signal. this.setFlag(Widget.Flag.IsDisposed); this._disposed.emit(undefined); // Remove or detach the widget if necessary. if (this.parent) { this.parent = null; } else if (this.isAttached) { Widget.detach(this); } // Dispose of the widget layout. if (this._layout) { this._layout.dispose(); this._layout = null; } // Clear the extra data associated with the widget. Signal.clearData(this); MessageLoop.clearData(this); AttachedProperty.clearData(this); } /** * A signal emitted when the widget is disposed. */ get disposed(): ISignal { return this._disposed; } /** * Get the DOM node owned by the widget. */ readonly node: HTMLElement; /** * Test whether the widget has been disposed. */ get isDisposed(): boolean { return this.testFlag(Widget.Flag.IsDisposed); } /** * Test whether the widget's node is attached to the DOM. */ get isAttached(): boolean { return this.testFlag(Widget.Flag.IsAttached); } /** * Test whether the widget is explicitly hidden. */ get isHidden(): boolean { return this.testFlag(Widget.Flag.IsHidden); } /** * Test whether the widget is visible. * * #### Notes * A widget is visible when it is attached to the DOM, is not * explicitly hidden, and has no explicitly hidden ancestors. */ get isVisible(): boolean { return this.testFlag(Widget.Flag.IsVisible); } /** * The title object for the widget. * * #### Notes * The title object is used by some container widgets when displaying * the widget alongside some title, such as a tab panel or side bar. * * Since not all widgets will use the title, it is created on demand. * * The `owner` property of the title is set to this widget. */ get title(): Title { return Private.titleProperty.get(this); } /** * Get the id of the widget's DOM node. */ get id(): string { return this.node.id; } /** * Set the id of the widget's DOM node. */ set id(value: string) { this.node.id = value; } /** * The dataset for the widget's DOM node. */ get dataset(): DOMStringMap { return this.node.dataset; } /** * Get the method for hiding the widget. */ get hiddenMode(): Widget.HiddenMode { return this._hiddenMode; } /** * Set the method for hiding the widget. */ set hiddenMode(value: Widget.HiddenMode) { if (this._hiddenMode === value) { return; } this._hiddenMode = value; switch (value) { case Widget.HiddenMode.Display: this.node.style.willChange = 'auto'; break; case Widget.HiddenMode.Scale: this.node.style.willChange = 'transform'; break; } if (this.isHidden) { if (value === Widget.HiddenMode.Display) { this.addClass('lm-mod-hidden'); /* */ this.addClass('p-mod-hidden'); /* */ this.node.style.transform = ''; } else { this.node.style.transform = 'scale(0)'; this.removeClass('lm-mod-hidden'); /* */ this.removeClass('p-mod-hidden'); /* */ } } } /** * Get the parent of the widget. */ get parent(): Widget | null { return this._parent; } /** * Set the parent of the widget. * * #### Notes * Children are typically added to a widget by using a layout, which * means user code will not normally set the parent widget directly. * * The widget will be automatically removed from its old parent. * * This is a no-op if there is no effective parent change. */ set parent(value: Widget | null) { if (this._parent === value) { return; } if (value && this.contains(value)) { throw new Error('Invalid parent widget.'); } if (this._parent && !this._parent.isDisposed) { let msg = new Widget.ChildMessage('child-removed', this); MessageLoop.sendMessage(this._parent, msg); } this._parent = value; if (this._parent && !this._parent.isDisposed) { let msg = new Widget.ChildMessage('child-added', this); MessageLoop.sendMessage(this._parent, msg); } if (!this.isDisposed) { MessageLoop.sendMessage(this, Widget.Msg.ParentChanged); } } /** * Get the layout for the widget. */ get layout(): Layout | null { return this._layout; } /** * Set the layout for the widget. * * #### Notes * The layout is single-use only. It cannot be changed after the * first assignment. * * The layout is disposed automatically when the widget is disposed. */ set layout(value: Layout | null) { if (this._layout === value) { return; } if (this.testFlag(Widget.Flag.DisallowLayout)) { throw new Error('Cannot set widget layout.'); } if (this._layout) { throw new Error('Cannot change widget layout.'); } if (value!.parent) { throw new Error('Cannot change layout parent.'); } this._layout = value; value!.parent = this; } /** * Create an iterator over the widget's children. * * @returns A new iterator over the children of the widget. * * #### Notes * The widget must have a populated layout in order to have children. * * If a layout is not installed, the returned iterator will be empty. */ children(): IIterator { return this._layout ? this._layout.iter() : empty(); } /** * Test whether a widget is a descendant of this widget. * * @param widget - The descendant widget of interest. * * @returns `true` if the widget is a descendant, `false` otherwise. */ contains(widget: Widget): boolean { for (let value: Widget | null = widget; value; value = value._parent) { if (value === this) { return true; } } return false; } /** * Test whether the widget's DOM node has the given class name. * * @param name - The class name of interest. * * @returns `true` if the node has the class, `false` otherwise. */ hasClass(name: string): boolean { return this.node.classList.contains(name); } /** * Add a class name to the widget's DOM node. * * @param name - The class name to add to the node. * * #### Notes * If the class name is already added to the node, this is a no-op. * * The class name must not contain whitespace. */ addClass(name: string): void { this.node.classList.add(name); } /** * Remove a class name from the widget's DOM node. * * @param name - The class name to remove from the node. * * #### Notes * If the class name is not yet added to the node, this is a no-op. * * The class name must not contain whitespace. */ removeClass(name: string): void { this.node.classList.remove(name); } /** * Toggle a class name on the widget's DOM node. * * @param name - The class name to toggle on the node. * * @param force - Whether to force add the class (`true`) or force * remove the class (`false`). If not provided, the presence of * the class will be toggled from its current state. * * @returns `true` if the class is now present, `false` otherwise. * * #### Notes * The class name must not contain whitespace. */ toggleClass(name: string, force?: boolean): boolean { if (force === true) { this.node.classList.add(name); return true; } if (force === false) { this.node.classList.remove(name); return false; } return this.node.classList.toggle(name); } /** * Post an `'update-request'` message to the widget. * * #### Notes * This is a simple convenience method for posting the message. */ update(): void { MessageLoop.postMessage(this, Widget.Msg.UpdateRequest); } /** * Post a `'fit-request'` message to the widget. * * #### Notes * This is a simple convenience method for posting the message. */ fit(): void { MessageLoop.postMessage(this, Widget.Msg.FitRequest); } /** * Post an `'activate-request'` message to the widget. * * #### Notes * This is a simple convenience method for posting the message. */ activate(): void { MessageLoop.postMessage(this, Widget.Msg.ActivateRequest); } /** * Send a `'close-request'` message to the widget. * * #### Notes * This is a simple convenience method for sending the message. */ close(): void { MessageLoop.sendMessage(this, Widget.Msg.CloseRequest); } /** * Show the widget and make it visible to its parent widget. * * #### Notes * This causes the [[isHidden]] property to be `false`. * * If the widget is not explicitly hidden, this is a no-op. */ show(): void { if (!this.testFlag(Widget.Flag.IsHidden)) { return; } if (this.isAttached && (!this.parent || this.parent.isVisible)) { MessageLoop.sendMessage(this, Widget.Msg.BeforeShow); } this.clearFlag(Widget.Flag.IsHidden); this.node.removeAttribute('aria-hidden'); if (this.hiddenMode === Widget.HiddenMode.Display) { this.removeClass('lm-mod-hidden'); /* */ this.removeClass('p-mod-hidden'); /* */ } else { this.node.style.transform = ''; } if (this.isAttached && (!this.parent || this.parent.isVisible)) { MessageLoop.sendMessage(this, Widget.Msg.AfterShow); } if (this.parent) { let msg = new Widget.ChildMessage('child-shown', this); MessageLoop.sendMessage(this.parent, msg); } } /** * Hide the widget and make it hidden to its parent widget. * * #### Notes * This causes the [[isHidden]] property to be `true`. * * If the widget is explicitly hidden, this is a no-op. */ hide(): void { if (this.testFlag(Widget.Flag.IsHidden)) { return; } if (this.isAttached && (!this.parent || this.parent.isVisible)) { MessageLoop.sendMessage(this, Widget.Msg.BeforeHide); } this.setFlag(Widget.Flag.IsHidden); this.node.setAttribute('aria-hidden', 'true'); if (this.hiddenMode === Widget.HiddenMode.Display) { this.addClass('lm-mod-hidden'); /* */ this.addClass('p-mod-hidden'); /* */ } else { this.node.style.transform = 'scale(0)'; } if (this.isAttached && (!this.parent || this.parent.isVisible)) { MessageLoop.sendMessage(this, Widget.Msg.AfterHide); } if (this.parent) { let msg = new Widget.ChildMessage('child-hidden', this); MessageLoop.sendMessage(this.parent, msg); } } /** * Show or hide the widget according to a boolean value. * * @param hidden - `true` to hide the widget, or `false` to show it. * * #### Notes * This is a convenience method for `hide()` and `show()`. */ setHidden(hidden: boolean): void { if (hidden) { this.hide(); } else { this.show(); } } /** * Test whether the given widget flag is set. * * #### Notes * This will not typically be called directly by user code. */ testFlag(flag: Widget.Flag): boolean { return (this._flags & flag) !== 0; } /** * Set the given widget flag. * * #### Notes * This will not typically be called directly by user code. */ setFlag(flag: Widget.Flag): void { this._flags |= flag; } /** * Clear the given widget flag. * * #### Notes * This will not typically be called directly by user code. */ clearFlag(flag: Widget.Flag): void { this._flags &= ~flag; } /** * Process a message sent to the widget. * * @param msg - The message sent to the widget. * * #### Notes * Subclasses may reimplement this method as needed. */ processMessage(msg: Message): void { switch (msg.type) { case 'resize': this.notifyLayout(msg); this.onResize(msg as Widget.ResizeMessage); break; case 'update-request': this.notifyLayout(msg); this.onUpdateRequest(msg); break; case 'fit-request': this.notifyLayout(msg); this.onFitRequest(msg); break; case 'before-show': this.notifyLayout(msg); this.onBeforeShow(msg); break; case 'after-show': this.setFlag(Widget.Flag.IsVisible); this.notifyLayout(msg); this.onAfterShow(msg); break; case 'before-hide': this.notifyLayout(msg); this.onBeforeHide(msg); break; case 'after-hide': this.clearFlag(Widget.Flag.IsVisible); this.notifyLayout(msg); this.onAfterHide(msg); break; case 'before-attach': this.notifyLayout(msg); this.onBeforeAttach(msg); break; case 'after-attach': if (!this.isHidden && (!this.parent || this.parent.isVisible)) { this.setFlag(Widget.Flag.IsVisible); } this.setFlag(Widget.Flag.IsAttached); this.notifyLayout(msg); this.onAfterAttach(msg); break; case 'before-detach': this.notifyLayout(msg); this.onBeforeDetach(msg); break; case 'after-detach': this.clearFlag(Widget.Flag.IsVisible); this.clearFlag(Widget.Flag.IsAttached); this.notifyLayout(msg); this.onAfterDetach(msg); break; case 'activate-request': this.notifyLayout(msg); this.onActivateRequest(msg); break; case 'close-request': this.notifyLayout(msg); this.onCloseRequest(msg); break; case 'child-added': this.notifyLayout(msg); this.onChildAdded(msg as Widget.ChildMessage); break; case 'child-removed': this.notifyLayout(msg); this.onChildRemoved(msg as Widget.ChildMessage); break; default: this.notifyLayout(msg); break; } } /** * Invoke the message processing routine of the widget's layout. * * @param msg - The message to dispatch to the layout. * * #### Notes * This is a no-op if the widget does not have a layout. * * This will not typically be called directly by user code. */ protected notifyLayout(msg: Message): void { if (this._layout) { this._layout.processParentMessage(msg); } } /** * A message handler invoked on a `'close-request'` message. * * #### Notes * The default implementation unparents or detaches the widget. */ protected onCloseRequest(msg: Message): void { if (this.parent) { this.parent = null; } else if (this.isAttached) { Widget.detach(this); } } /** * A message handler invoked on a `'resize'` message. * * #### Notes * The default implementation of this handler is a no-op. */ protected onResize(msg: Widget.ResizeMessage): void {} /** * A message handler invoked on an `'update-request'` message. * * #### Notes * The default implementation of this handler is a no-op. */ protected onUpdateRequest(msg: Message): void {} /** * A message handler invoked on a `'fit-request'` message. * * #### Notes * The default implementation of this handler is a no-op. */ protected onFitRequest(msg: Message): void {} /** * A message handler invoked on an `'activate-request'` message. * * #### Notes * The default implementation of this handler is a no-op. */ protected onActivateRequest(msg: Message): void {} /** * A message handler invoked on a `'before-show'` message. * * #### Notes * The default implementation of this handler is a no-op. */ protected onBeforeShow(msg: Message): void {} /** * A message handler invoked on an `'after-show'` message. * * #### Notes * The default implementation of this handler is a no-op. */ protected onAfterShow(msg: Message): void {} /** * A message handler invoked on a `'before-hide'` message. * * #### Notes * The default implementation of this handler is a no-op. */ protected onBeforeHide(msg: Message): void {} /** * A message handler invoked on an `'after-hide'` message. * * #### Notes * The default implementation of this handler is a no-op. */ protected onAfterHide(msg: Message): void {} /** * A message handler invoked on a `'before-attach'` message. * * #### Notes * The default implementation of this handler is a no-op. */ protected onBeforeAttach(msg: Message): void {} /** * A message handler invoked on an `'after-attach'` message. * * #### Notes * The default implementation of this handler is a no-op. */ protected onAfterAttach(msg: Message): void {} /** * A message handler invoked on a `'before-detach'` message. * * #### Notes * The default implementation of this handler is a no-op. */ protected onBeforeDetach(msg: Message): void {} /** * A message handler invoked on an `'after-detach'` message. * * #### Notes * The default implementation of this handler is a no-op. */ protected onAfterDetach(msg: Message): void {} /** * A message handler invoked on a `'child-added'` message. * * #### Notes * The default implementation of this handler is a no-op. */ protected onChildAdded(msg: Widget.ChildMessage): void {} /** * A message handler invoked on a `'child-removed'` message. * * #### Notes * The default implementation of this handler is a no-op. */ protected onChildRemoved(msg: Widget.ChildMessage): void {} private _flags = 0; private _layout: Layout | null = null; private _parent: Widget | null = null; private _disposed = new Signal(this); private _hiddenMode: Widget.HiddenMode = Widget.HiddenMode.Display; } /** * The namespace for the `Widget` class statics. */ export namespace Widget { /** * An options object for initializing a widget. */ export interface IOptions { /** * The optional node to use for the widget. * * If a node is provided, the widget will assume full ownership * and control of the node, as if it had created the node itself. * * The default is a new `
    `. */ node?: HTMLElement; /** * The optional element tag, used for constructing the widget's node. * * If a pre-constructed node is provided via the `node` arg, this * value is ignored. */ tag?: keyof HTMLElementTagNameMap; } /** * The method for hiding the widget. * * The default is Display. * * Using `Scale` will often increase performance as most browsers will not * trigger style computation for the `transform` action. This should be used * sparingly and tested, since increasing the number of composition layers * may slow things down. * * To ensure the transformation does not trigger style recomputation, you * may need to set the widget CSS style `will-change: transform`. This * should be used only when needed as it may overwhelm the browser with a * high number of layers. See * https://developer.mozilla.org/en-US/docs/Web/CSS/will-change */ export enum HiddenMode { /** * Set a `lm-mod-hidden` CSS class to hide the widget using `display:none` * CSS from the standard Lumino CSS. */ Display = 0, /** * Hide the widget by setting the `transform` to `'scale(0)'`. */ Scale } /** * An enum of widget bit flags. */ export enum Flag { /** * The widget has been disposed. */ IsDisposed = 0x1, /** * The widget is attached to the DOM. */ IsAttached = 0x2, /** * The widget is hidden. */ IsHidden = 0x4, /** * The widget is visible. */ IsVisible = 0x8, /** * A layout cannot be set on the widget. */ DisallowLayout = 0x10 } /** * A collection of stateless messages related to widgets. */ export namespace Msg { /** * A singleton `'before-show'` message. * * #### Notes * This message is sent to a widget before it becomes visible. * * This message is **not** sent when the widget is being attached. */ export const BeforeShow = new Message('before-show'); /** * A singleton `'after-show'` message. * * #### Notes * This message is sent to a widget after it becomes visible. * * This message is **not** sent when the widget is being attached. */ export const AfterShow = new Message('after-show'); /** * A singleton `'before-hide'` message. * * #### Notes * This message is sent to a widget before it becomes not-visible. * * This message is **not** sent when the widget is being detached. */ export const BeforeHide = new Message('before-hide'); /** * A singleton `'after-hide'` message. * * #### Notes * This message is sent to a widget after it becomes not-visible. * * This message is **not** sent when the widget is being detached. */ export const AfterHide = new Message('after-hide'); /** * A singleton `'before-attach'` message. * * #### Notes * This message is sent to a widget before it is attached. */ export const BeforeAttach = new Message('before-attach'); /** * A singleton `'after-attach'` message. * * #### Notes * This message is sent to a widget after it is attached. */ export const AfterAttach = new Message('after-attach'); /** * A singleton `'before-detach'` message. * * #### Notes * This message is sent to a widget before it is detached. */ export const BeforeDetach = new Message('before-detach'); /** * A singleton `'after-detach'` message. * * #### Notes * This message is sent to a widget after it is detached. */ export const AfterDetach = new Message('after-detach'); /** * A singleton `'parent-changed'` message. * * #### Notes * This message is sent to a widget when its parent has changed. */ export const ParentChanged = new Message('parent-changed'); /** * A singleton conflatable `'update-request'` message. * * #### Notes * This message can be dispatched to supporting widgets in order to * update their content based on the current widget state. Not all * widgets will respond to messages of this type. * * For widgets with a layout, this message will inform the layout to * update the position and size of its child widgets. */ export const UpdateRequest = new ConflatableMessage('update-request'); /** * A singleton conflatable `'fit-request'` message. * * #### Notes * For widgets with a layout, this message will inform the layout to * recalculate its size constraints to fit the space requirements of * its child widgets, and to update their position and size. Not all * layouts will respond to messages of this type. */ export const FitRequest = new ConflatableMessage('fit-request'); /** * A singleton conflatable `'activate-request'` message. * * #### Notes * This message should be dispatched to a widget when it should * perform the actions necessary to activate the widget, which * may include focusing its node or descendant node. */ export const ActivateRequest = new ConflatableMessage('activate-request'); /** * A singleton conflatable `'close-request'` message. * * #### Notes * This message should be dispatched to a widget when it should close * and remove itself from the widget hierarchy. */ export const CloseRequest = new ConflatableMessage('close-request'); } /** * A message class for child related messages. */ export class ChildMessage extends Message { /** * Construct a new child message. * * @param type - The message type. * * @param child - The child widget for the message. */ constructor(type: string, child: Widget) { super(type); this.child = child; } /** * The child widget for the message. */ readonly child: Widget; } /** * A message class for `'resize'` messages. */ export class ResizeMessage extends Message { /** * Construct a new resize message. * * @param width - The **offset width** of the widget, or `-1` if * the width is not known. * * @param height - The **offset height** of the widget, or `-1` if * the height is not known. */ constructor(width: number, height: number) { super('resize'); this.width = width; this.height = height; } /** * The offset width of the widget. * * #### Notes * This will be `-1` if the width is unknown. */ readonly width: number; /** * The offset height of the widget. * * #### Notes * This will be `-1` if the height is unknown. */ readonly height: number; } /** * The namespace for the `ResizeMessage` class statics. */ export namespace ResizeMessage { /** * A singleton `'resize'` message with an unknown size. */ export const UnknownSize = new ResizeMessage(-1, -1); } /** * Attach a widget to a host DOM node. * * @param widget - The widget of interest. * * @param host - The DOM node to use as the widget's host. * * @param ref - The child of `host` to use as the reference element. * If this is provided, the widget will be inserted before this * node in the host. The default is `null`, which will cause the * widget to be added as the last child of the host. * * #### Notes * This will throw an error if the widget is not a root widget, if * the widget is already attached, or if the host is not attached * to the DOM. */ export function attach( widget: Widget, host: HTMLElement, ref: HTMLElement | null = null ): void { if (widget.parent) { throw new Error('Cannot attach a child widget.'); } if (widget.isAttached || document.body.contains(widget.node)) { throw new Error('Widget is already attached.'); } if (!document.body.contains(host)) { throw new Error('Host is not attached.'); } MessageLoop.sendMessage(widget, Widget.Msg.BeforeAttach); host.insertBefore(widget.node, ref); MessageLoop.sendMessage(widget, Widget.Msg.AfterAttach); } /** * Detach the widget from its host DOM node. * * @param widget - The widget of interest. * * #### Notes * This will throw an error if the widget is not a root widget, * or if the widget is not attached to the DOM. */ export function detach(widget: Widget): void { if (widget.parent) { throw new Error('Cannot detach a child widget.'); } if (!widget.isAttached || !document.body.contains(widget.node)) { throw new Error('Widget is not attached.'); } MessageLoop.sendMessage(widget, Widget.Msg.BeforeDetach); widget.node.parentNode!.removeChild(widget.node); MessageLoop.sendMessage(widget, Widget.Msg.AfterDetach); } } /** * The namespace for the module implementation details. */ namespace Private { /** * An attached property for the widget title object. */ export const titleProperty = new AttachedProperty>({ name: 'title', create: owner => new Title({ owner }) }); /** * Create a DOM node for the given widget options. */ export function createNode(options: Widget.IOptions): HTMLElement { return options.node || document.createElement(options.tag || 'div'); } } lumino-2021.12.13/packages/widgets/style/000077500000000000000000000000001415564225700200055ustar00rootroot00000000000000lumino-2021.12.13/packages/widgets/style/accordionpanel.css000066400000000000000000000003611415564225700235000ustar00rootroot00000000000000.lm-AccordionPanel[data-orientation='horizontal'] > .lm-AccordionPanel-title { /* Title is rotated for horizontal accordion panel using CSS */ display: block; transform-origin: top left; transform: rotate(-90deg) translate(-100%); } lumino-2021.12.13/packages/widgets/style/commandpalette.css000066400000000000000000000043101415564225700235120ustar00rootroot00000000000000/*----------------------------------------------------------------------------- | Copyright (c) Jupyter Development Team. | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ /* */ .p-CommandPalette, /* */ .lm-CommandPalette { display: flex; flex-direction: column; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; } /* */ .p-CommandPalette-search, /* */ .lm-CommandPalette-search { flex: 0 0 auto; } /* */ .p-CommandPalette-content, /* */ .lm-CommandPalette-content { flex: 1 1 auto; margin: 0; padding: 0; min-height: 0; overflow: auto; list-style-type: none; } /* */ .p-CommandPalette-header, /* */ .lm-CommandPalette-header { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; } /* */ .p-CommandPalette-item, /* */ .lm-CommandPalette-item { display: flex; flex-direction: row; } /* */ .p-CommandPalette-itemIcon, /* */ .lm-CommandPalette-itemIcon { flex: 0 0 auto; } /* */ .p-CommandPalette-itemContent, /* */ .lm-CommandPalette-itemContent { flex: 1 1 auto; overflow: hidden; } /* */ .p-CommandPalette-itemShortcut, /* */ .lm-CommandPalette-itemShortcut { flex: 0 0 auto; } /* */ .p-CommandPalette-itemLabel, /* */ .lm-CommandPalette-itemLabel { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; } .lm-close-icon { border: 1px solid transparent; background-color: transparent; position: absolute; z-index: 1; right: 3%; top: 0; bottom: 0; margin: auto; padding: 7px 0; display: none; vertical-align: middle; outline: 0; cursor: pointer; } .lm-close-icon:after { content: 'X'; display: block; width: 15px; height: 15px; text-align: center; color: #000; font-weight: normal; font-size: 12px; cursor: pointer; } lumino-2021.12.13/packages/widgets/style/dockpanel.css000066400000000000000000000041761415564225700224670ustar00rootroot00000000000000/*----------------------------------------------------------------------------- | Copyright (c) Jupyter Development Team. | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ /* */ .p-DockPanel, /* */ .lm-DockPanel { z-index: 0; } /* */ .p-DockPanel-widget, /* */ .lm-DockPanel-widget { z-index: 0; } /* */ .p-DockPanel-tabBar, /* */ .lm-DockPanel-tabBar { z-index: 1; } /* */ .p-DockPanel-handle, /* */ .lm-DockPanel-handle { z-index: 2; } /* */ .p-DockPanel-handle.p-mod-hidden, /* */ .lm-DockPanel-handle.lm-mod-hidden { display: none !important; } /* */ .p-DockPanel-handle:after, /* */ .lm-DockPanel-handle:after { position: absolute; top: 0; left: 0; width: 100%; height: 100%; content: ''; } /* */ .p-DockPanel-handle[data-orientation='horizontal'], /* */ .lm-DockPanel-handle[data-orientation='horizontal'] { cursor: ew-resize; } /* */ .p-DockPanel-handle[data-orientation='vertical'], /* */ .lm-DockPanel-handle[data-orientation='vertical'] { cursor: ns-resize; } /* */ .p-DockPanel-handle[data-orientation='horizontal']:after, /* */ .lm-DockPanel-handle[data-orientation='horizontal']:after { left: 50%; min-width: 8px; transform: translateX(-50%); } /* */ .p-DockPanel-handle[data-orientation='vertical']:after, /* */ .lm-DockPanel-handle[data-orientation='vertical']:after { top: 50%; min-height: 8px; transform: translateY(-50%); } /* */ .p-DockPanel-overlay, /* */ .lm-DockPanel-overlay { z-index: 3; box-sizing: border-box; pointer-events: none; } /* */ .p-DockPanel-overlay.p-mod-hidden, /* */ .lm-DockPanel-overlay.lm-mod-hidden { display: none !important; } lumino-2021.12.13/packages/widgets/style/index.css000066400000000000000000000012221415564225700216230ustar00rootroot00000000000000/*----------------------------------------------------------------------------- | Copyright (c) Jupyter Development Team. | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ @import './widget.css'; @import './accordionpanel.css'; @import './commandpalette.css'; @import './dockpanel.css'; @import './menu.css'; @import './menubar.css'; @import './scrollbar.css'; @import './splitpanel.css'; @import './tabbar.css'; @import './tabpanel.css'; lumino-2021.12.13/packages/widgets/style/index.js000066400000000000000000000006361415564225700214570ustar00rootroot00000000000000/*----------------------------------------------------------------------------- | Copyright (c) Jupyter Development Team. | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import './index.css'; lumino-2021.12.13/packages/widgets/style/menu.css000066400000000000000000000030041415564225700214600ustar00rootroot00000000000000/*----------------------------------------------------------------------------- | Copyright (c) Jupyter Development Team. | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ /* */ .p-Menu, /* */ .lm-Menu { z-index: 10000; position: absolute; white-space: nowrap; overflow-x: hidden; overflow-y: auto; outline: none; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; } /* */ .p-Menu-content, /* */ .lm-Menu-content { margin: 0; padding: 0; display: table; list-style-type: none; } /* */ .p-Menu-item, /* */ .lm-Menu-item { display: table-row; } /* */ .p-Menu-item.p-mod-hidden, .p-Menu-item.p-mod-collapsed, /* */ .lm-Menu-item.lm-mod-hidden, .lm-Menu-item.lm-mod-collapsed { display: none !important; } /* */ .p-Menu-itemIcon, .p-Menu-itemSubmenuIcon, /* */ .lm-Menu-itemIcon, .lm-Menu-itemSubmenuIcon { display: table-cell; text-align: center; } /* */ .p-Menu-itemLabel, /* */ .lm-Menu-itemLabel { display: table-cell; text-align: left; } /* */ .p-Menu-itemShortcut, /* */ .lm-Menu-itemShortcut { display: table-cell; text-align: right; } lumino-2021.12.13/packages/widgets/style/menubar.css000066400000000000000000000017661415564225700221620ustar00rootroot00000000000000/*----------------------------------------------------------------------------- | Copyright (c) Jupyter Development Team. | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ /* */ .p-MenuBar, /* */ .lm-MenuBar { outline: none; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; } /* */ .p-MenuBar-content, /* */ .lm-MenuBar-content { margin: 0; padding: 0; display: flex; flex-direction: row; list-style-type: none; } /* */ .p--MenuBar-item, /* */ .lm-MenuBar-item { box-sizing: border-box; } /* */ .p-MenuBar-itemIcon, .p-MenuBar-itemLabel, /* */ .lm-MenuBar-itemIcon, .lm-MenuBar-itemLabel { display: inline-block; } lumino-2021.12.13/packages/widgets/style/scrollbar.css000066400000000000000000000024561415564225700225110ustar00rootroot00000000000000/*----------------------------------------------------------------------------- | Copyright (c) Jupyter Development Team. | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ /* */ .p-ScrollBar, /* */ .lm-ScrollBar { display: flex; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; } /* */ .p-ScrollBar[data-orientation='horizontal'], /* */ .lm-ScrollBar[data-orientation='horizontal'] { flex-direction: row; } /* */ .p-ScrollBar[data-orientation='vertical'], /* */ .lm-ScrollBar[data-orientation='vertical'] { flex-direction: column; } /* */ .p-ScrollBar-button, /* */ .lm-ScrollBar-button { box-sizing: border-box; flex: 0 0 auto; } /* */ .p-ScrollBar-track, /* */ .lm-ScrollBar-track { box-sizing: border-box; position: relative; overflow: hidden; flex: 1 1 auto; } /* */ .p-ScrollBar-thumb, /* */ .lm-ScrollBar-thumb { box-sizing: border-box; position: absolute; } lumino-2021.12.13/packages/widgets/style/splitpanel.css000066400000000000000000000034571415564225700227030ustar00rootroot00000000000000/*----------------------------------------------------------------------------- | Copyright (c) Jupyter Development Team. | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ /* */ .p-SplitPanel-child, /* */ .lm-SplitPanel-child { z-index: 0; } /* */ .p-SplitPanel-handle, /* */ .lm-SplitPanel-handle { z-index: 1; } /* */ .p-SplitPanel-handle.p-mod-hidden, /* */ .lm-SplitPanel-handle.lm-mod-hidden { display: none !important; } /* */ .p-SplitPanel-handle:after, /* */ .lm-SplitPanel-handle:after { position: absolute; top: 0; left: 0; width: 100%; height: 100%; content: ''; } /* */ .p-SplitPanel[data-orientation='horizontal'] > .p-SplitPanel-handle, /* */ .lm-SplitPanel[data-orientation='horizontal'] > .lm-SplitPanel-handle { cursor: ew-resize; } /* */ .p-SplitPanel[data-orientation='vertical'] > .p-SplitPanel-handle, /* */ .lm-SplitPanel[data-orientation='vertical'] > .lm-SplitPanel-handle { cursor: ns-resize; } /* */ .p-SplitPanel[data-orientation='horizontal'] > .p-SplitPanel-handle:after, /* */ .lm-SplitPanel[data-orientation='horizontal'] > .lm-SplitPanel-handle:after { left: 50%; min-width: 8px; transform: translateX(-50%); } /* */ .p-SplitPanel[data-orientation='vertical'] > .p-SplitPanel-handle:after, /* */ .lm-SplitPanel[data-orientation='vertical'] > .lm-SplitPanel-handle:after { top: 50%; min-height: 8px; transform: translateY(-50%); } lumino-2021.12.13/packages/widgets/style/tabbar.css000066400000000000000000000061501415564225700217540ustar00rootroot00000000000000/*----------------------------------------------------------------------------- | Copyright (c) Jupyter Development Team. | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ /* */ .p-TabBar, /* */ .lm-TabBar { display: flex; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; } /* */ .p-TabBar[data-orientation='horizontal'], /* */ .lm-TabBar[data-orientation='horizontal'] { flex-direction: row; align-items: flex-end; } /* */ .p-TabBar[data-orientation='vertical'], /* */ .lm-TabBar[data-orientation='vertical'] { flex-direction: column; align-items: flex-end; } /* */ .p-TabBar-content, /* */ .lm-TabBar-content { margin: 0; padding: 0; display: flex; flex: 1 1 auto; list-style-type: none; } /* */ .p-TabBar[data-orientation='horizontal'] > .p-TabBar-content, /* */ .lm-TabBar[data-orientation='horizontal'] > .lm-TabBar-content { flex-direction: row; } /* */ .p-TabBar[data-orientation='vertical'] > .p-TabBar-content, /* */ .lm-TabBar[data-orientation='vertical'] > .lm-TabBar-content { flex-direction: column; } /* */ .p-TabBar-tab, /* */ .lm-TabBar-tab { display: flex; flex-direction: row; box-sizing: border-box; overflow: hidden; touch-action: none; /* Disable native Drag/Drop */ } /* */ .p-TabBar-tabIcon, .p-TabBar-tabCloseIcon, /* */ .lm-TabBar-tabIcon, .lm-TabBar-tabCloseIcon { flex: 0 0 auto; } /* */ .p-TabBar-tabLabel, /* */ .lm-TabBar-tabLabel { flex: 1 1 auto; overflow: hidden; white-space: nowrap; } .lm-TabBar-tabInput { user-select: all; width: 100%; box-sizing: border-box; } /* */ .p-TabBar-tab.p-mod-hidden, /* */ .lm-TabBar-tab.lm-mod-hidden { display: none !important; } .lm-TabBar-addButton.lm-mod-hidden { display: none !important; } /* */ .p-TabBar.p-mod-dragging .p-TabBar-tab, /* */ .lm-TabBar.lm-mod-dragging .lm-TabBar-tab { position: relative; } /* */ .p-TabBar.p-mod-dragging[data-orientation='horizontal'] .p-TabBar-tab, /* */ .lm-TabBar.lm-mod-dragging[data-orientation='horizontal'] .lm-TabBar-tab { left: 0; transition: left 150ms ease; } /* */ .p-TabBar.p-mod-dragging[data-orientation='vertical'] .p-TabBar-tab, /* */ .lm-TabBar.lm-mod-dragging[data-orientation='vertical'] .lm-TabBar-tab { top: 0; transition: top 150ms ease; } /* */ .p-TabBar.p-mod-dragging .p-TabBar-tab.p-mod-dragging, /* */ .lm-TabBar.lm-mod-dragging .lm-TabBar-tab.lm-mod-dragging { transition: none; } .lm-TabBar-tabLabel .lm-TabBar-tabInput { user-select: all; width: 100%; box-sizing: border-box; background: inherit; } lumino-2021.12.13/packages/widgets/style/tabpanel.css000066400000000000000000000011271415564225700223060ustar00rootroot00000000000000/*----------------------------------------------------------------------------- | Copyright (c) Jupyter Development Team. | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ /* */ .p-TabPanel-tabBar, /* */ .lm-TabPanel-tabBar { z-index: 1; } /* */ .p-TabPanel-stackedPanel, /* */ .lm-TabPanel-stackedPanel { z-index: 0; } lumino-2021.12.13/packages/widgets/style/widget.css000066400000000000000000000012311415564225700217770ustar00rootroot00000000000000/*----------------------------------------------------------------------------- | Copyright (c) Jupyter Development Team. | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ /* */ .p-Widget, /* */ .lm-Widget { box-sizing: border-box; position: relative; overflow: hidden; cursor: default; } /* */ .p-Widget.p-mod-hidden, /* */ .lm-Widget.lm-mod-hidden { display: none !important; } lumino-2021.12.13/packages/widgets/tdoptions.json000066400000000000000000000005161415564225700215650ustar00rootroot00000000000000{ "excludeNotExported": true, "mode": "file", "target": "es5", "module": "es5", "lib": [ "lib.es2015.d.ts", "lib.es2015.collection.d.ts", "lib.es2015.promise.d.ts", "lib.dom.d.ts" ], "out": "../../docs/source/api/widgets", "baseUrl": ".", "paths": { "@lumino/*": ["node_modules/@lumino/*"] } } lumino-2021.12.13/packages/widgets/tests/000077500000000000000000000000001415564225700200075ustar00rootroot00000000000000lumino-2021.12.13/packages/widgets/tests/karma.conf.js000066400000000000000000000005051415564225700223640ustar00rootroot00000000000000module.exports = function (config) { config.set({ basePath: '.', frameworks: ['mocha'], reporters: ['mocha'], files: ['build/bundle.test.js'], port: 9876, colors: true, singleRun: true, browserNoActivityTimeout: 30000, failOnEmptyTestSuite: false, logLevel: config.LOG_INFO }); }; lumino-2021.12.13/packages/widgets/tests/src/000077500000000000000000000000001415564225700205765ustar00rootroot00000000000000lumino-2021.12.13/packages/widgets/tests/src/accordionlayout.spec.ts000066400000000000000000000143331415564225700253020ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. import { each, every } from '@lumino/algorithm'; import { Message } from '@lumino/messaging'; import { AccordionLayout, Title, Widget } from '@lumino/widgets'; import { expect } from 'chai'; const renderer: AccordionLayout.IRenderer = { titleClassName: '.lm-AccordionTitle', createHandle: () => document.createElement('div'), createSectionTitle: (title: Title) => document.createElement('h3') }; class LogAccordionLayout extends AccordionLayout { methods: string[] = []; protected init(): void { super.init(); this.methods.push('init'); } protected attachWidget(index: number, widget: Widget): void { super.attachWidget(index, widget); this.methods.push('attachWidget'); } protected moveWidget( fromIndex: number, toIndex: number, widget: Widget ): void { super.moveWidget(fromIndex, toIndex, widget); this.methods.push('moveWidget'); } protected detachWidget(index: number, widget: Widget): void { super.detachWidget(index, widget); this.methods.push('detachWidget'); } protected onFitRequest(msg: Message): void { super.onFitRequest(msg); this.methods.push('onFitRequest'); } } describe('@lumino/widgets', () => { describe('AccordionLayout', () => { describe('#constructor()', () => { it('should accept a renderer', () => { const layout = new AccordionLayout({ renderer }); expect(layout).to.be.an.instanceof(AccordionLayout); }); it('should be vertical by default', () => { const layout = new AccordionLayout({ renderer }); expect(layout.orientation).to.equal('vertical'); }); }); describe('#titleSpace', () => { it('should get the inter-element spacing for the split layout', () => { const layout = new AccordionLayout({ renderer }); expect(layout.titleSpace).to.equal(22); }); it('should set the inter-element spacing for the split layout', () => { const layout = new AccordionLayout({ renderer }); layout.titleSpace = 10; expect(layout.titleSpace).to.equal(10); }); it('should post a fit request to the parent widget', done => { let layout = new LogAccordionLayout({ renderer }); let parent = new Widget(); parent.layout = layout; layout.titleSpace = 10; requestAnimationFrame(() => { expect(layout.methods).to.contain('onFitRequest'); done(); }); }); it('should be a no-op if the value does not change', done => { let layout = new LogAccordionLayout({ renderer }); let parent = new Widget(); parent.layout = layout; layout.titleSpace = 22; requestAnimationFrame(() => { expect(layout.methods).to.not.contain('onFitRequest'); done(); }); }); }); describe('#renderer', () => { it('should get the renderer for the layout', () => { const layout = new AccordionLayout({ renderer }); expect(layout.renderer).to.equal(renderer); }); }); describe('#titles', () => { it('should be a read-only sequence of the accordion titles in the layout', () => { const layout = new AccordionLayout({ renderer }); let parent = new Widget(); parent.layout = layout; const widgets = [new Widget(), new Widget(), new Widget()]; each(widgets, w => { layout.addWidget(w); }); expect(every(layout.titles, h => h instanceof HTMLElement)); expect(layout.titles).to.have.length(widgets.length); }); }); describe('#attachWidget()', () => { it('should insert a title node before the widget', () => { let layout = new LogAccordionLayout({ renderer }); let parent = new Widget(); parent.layout = layout; let widget = new Widget(); layout.addWidget(widget); expect(layout.methods).to.contain('attachWidget'); expect(parent.node.contains(widget.node)).to.equal(true); expect(layout.titles.length).to.equal(1); const title = layout.titles[0]; expect(widget.node.previousElementSibling).to.equal(title); expect(title.getAttribute('aria-label')).to.equal( `${parent.title.label} Section` ); expect(title.getAttribute('aria-expanded')).to.equal('true'); expect(title.classList.contains('lm-mod-expanded')).to.be.true; expect(widget.node.getAttribute('role')).to.equal('region'); expect(widget.node.getAttribute('aria-labelledby')).to.equal(title.id); parent.dispose(); }); }); describe('#moveWidget()', () => { it("should move a title in the parent's DOM node", () => { let layout = new LogAccordionLayout({ renderer }); let widgets = [new Widget(), new Widget(), new Widget()]; let parent = new Widget(); parent.layout = layout; widgets.forEach(w => { layout.addWidget(w); }); let widget = widgets[0]; let title = layout.titles[0]; layout.insertWidget(2, widget); expect(layout.methods).to.contain('moveWidget'); expect(layout.titles[2]).to.equal(title); parent.dispose(); }); }); describe('#detachWidget()', () => { it("should detach a title from the parent's DOM node", () => { let layout = new LogAccordionLayout({ renderer }); let widget = new Widget(); let parent = new Widget(); parent.layout = layout; layout.addWidget(widget); const title = layout.titles[0]; layout.removeWidget(widget); expect(layout.methods).to.contain('detachWidget'); expect(parent.node.contains(title)).to.equal(false); expect(layout.titles).to.have.length(0); parent.dispose(); }); }); describe('#dispose', () => { it('clear the titles list', () => { const layout = new AccordionLayout({ renderer }); const widgets = [new Widget(), new Widget(), new Widget()]; widgets.forEach(w => { layout.addWidget(w); }); layout.dispose(); expect(layout.titles).to.have.length(0); }); }); }); }); lumino-2021.12.13/packages/widgets/tests/src/accordionpanel.spec.ts000066400000000000000000000257701415564225700250730ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. import { every } from '@lumino/algorithm'; import { MessageLoop } from '@lumino/messaging'; import { AccordionLayout, AccordionPanel, Title, Widget } from '@lumino/widgets'; import { expect } from 'chai'; import { simulate } from 'simulate-event'; const renderer: AccordionPanel.IRenderer = { titleClassName: '.lm-AccordionTitle', createHandle: () => document.createElement('div'), createSectionTitle: (title: Title) => document.createElement('h3') }; class LogAccordionPanel extends AccordionPanel { events: string[] = []; handleEvent(event: Event): void { super.handleEvent(event); this.events.push(event.type); } } describe('@lumino/widgets', () => { describe('AccordionPanel', () => { describe('#constructor()', () => { it('should accept no arguments', () => { let panel = new AccordionPanel(); expect(panel).to.be.an.instanceof(AccordionPanel); }); it('should accept options', () => { let panel = new AccordionPanel({ orientation: 'horizontal', spacing: 5, titleSpace: 42, renderer }); expect(panel.orientation).to.equal('horizontal'); expect(panel.spacing).to.equal(5); expect(panel.titleSpace).to.equal(42); expect(panel.renderer).to.equal(renderer); }); it('should accept a layout option', () => { let layout = new AccordionLayout({ renderer }); let panel = new AccordionPanel({ layout }); expect(panel.layout).to.equal(layout); }); it('should ignore other options if a layout is given', () => { let ignored = Object.create(renderer); let layout = new AccordionLayout({ renderer }); let panel = new AccordionPanel({ layout, orientation: 'horizontal', spacing: 5, titleSpace: 42, renderer: ignored }); expect(panel.layout).to.equal(layout); expect(panel.orientation).to.equal('vertical'); expect(panel.spacing).to.equal(4); expect(panel.titleSpace).to.equal(22); expect(panel.renderer).to.equal(renderer); }); it('should add the `lm-AccordionPanel` class', () => { let panel = new AccordionPanel(); expect(panel.hasClass('lm-AccordionPanel')).to.equal(true); }); }); describe('#dispose()', () => { it('should dispose of the resources held by the panel', () => { let panel = new LogAccordionPanel(); let layout = panel.layout as AccordionLayout; let widgets = [new Widget(), new Widget(), new Widget()]; widgets.forEach(w => { panel.addWidget(w); }); Widget.attach(panel, document.body); panel.dispose(); expect(every(widgets, w => w.isDisposed)); expect(layout.titles).to.have.length(0); }); }); describe('#titleSpace', () => { it('should default to `22`', () => { let panel = new AccordionPanel(); expect(panel.titleSpace).to.equal(22); }); it('should set the titleSpace for the panel', () => { let panel = new AccordionPanel(); panel.titleSpace = 10; expect(panel.titleSpace).to.equal(10); }); }); describe('#renderer', () => { it('should get the renderer for the panel', () => { let panel = new AccordionPanel({ renderer }); expect(panel.renderer).to.equal(renderer); }); }); describe('#titles', () => { it('should get the read-only sequence of the accordion titles in the panel', () => { let panel = new AccordionPanel(); let widgets = [new Widget(), new Widget(), new Widget()]; widgets.forEach(w => { panel.addWidget(w); }); expect(panel.titles.length).to.equal(widgets.length); }); it('should update the title element', () => { const text = 'Something'; let panel = new AccordionPanel(); let widget = new Widget(); panel.addWidget(widget); widget.title.label = text; const el = panel.titles[0].querySelector( '.lm-AccordionPanel-titleLabel' )!; expect(el.textContent).to.equal(text); }); }); describe('#handleEvent()', () => { let panel: LogAccordionPanel; let layout: AccordionLayout; beforeEach(() => { panel = new LogAccordionPanel(); layout = panel.layout as AccordionLayout; let widgets = [new Widget(), new Widget(), new Widget()]; widgets.forEach(w => { panel.addWidget(w); }); panel.setRelativeSizes([10, 10, 10, 20]); Widget.attach(panel, document.body); MessageLoop.flush(); }); afterEach(() => { panel.dispose(); }); context('click', () => { it('should collapse an expanded widget', () => { simulate(layout.titles[0], 'click'); expect(panel.events).to.contain('click'); expect(layout.titles[0].getAttribute('aria-expanded')).to.equal( 'false' ); expect(layout.titles[0].classList.contains('lm-mod-expanded')).to.be .false; expect(layout.widgets[0].isHidden).to.be.true; }); it('should expand a collapsed widget', () => { // Collapse simulate(layout.titles[0], 'click'); simulate(layout.titles[0], 'click'); expect(layout.titles[0].getAttribute('aria-expanded')).to.equal( 'true' ); expect(layout.titles[0].classList.contains('lm-mod-expanded')).to.be .true; expect(layout.widgets[0].isHidden).to.be.false; }); }); context('keydown', () => { it('should redirect to toggle expansion state if Space is pressed', () => { simulate(layout.titles[0], 'keydown', { key: 'Space' }); expect(panel.events).to.contain('keydown'); expect(layout.titles[0].getAttribute('aria-expanded')).to.equal( 'false' ); expect(layout.titles[0].classList.contains('lm-mod-expanded')).to.be .false; expect(layout.widgets[0].isHidden).to.be.true; simulate(layout.titles[0], 'keydown', { key: 'Space' }); expect(panel.events).to.contain('keydown'); expect(layout.titles[0].getAttribute('aria-expanded')).to.equal( 'true' ); expect(layout.titles[0].classList.contains('lm-mod-expanded')).to.be .true; expect(layout.widgets[0].isHidden).to.be.false; }); it('should redirect to toggle expansion state if Enter is pressed', () => { simulate(layout.titles[0], 'keydown', { key: 'Enter' }); expect(panel.events).to.contain('keydown'); expect(layout.titles[0].getAttribute('aria-expanded')).to.equal( 'false' ); expect(layout.titles[0].classList.contains('lm-mod-expanded')).to.be .false; expect(layout.widgets[0].isHidden).to.be.true; simulate(layout.titles[0], 'keydown', { key: 'Enter' }); expect(panel.events).to.contain('keydown'); expect(layout.titles[0].getAttribute('aria-expanded')).to.equal( 'true' ); expect(layout.titles[0].classList.contains('lm-mod-expanded')).to.be .true; expect(layout.widgets[0].isHidden).to.be.false; }); it('should focus on the next widget if Arrow Down is pressed', () => { layout.titles[1].focus(); simulate(layout.titles[1], 'keydown', { key: 'ArrowDown' }); expect(panel.events).to.contain('keydown'); expect(document.activeElement).to.be.equal(layout.titles[2]); }); it('should focus on the previous widget if Arrow Up is pressed', () => { layout.titles[1].focus(); simulate(layout.titles[1], 'keydown', { key: 'ArrowUp' }); expect(panel.events).to.contain('keydown'); expect(document.activeElement).to.be.equal(layout.titles[0]); }); it('should focus on the first widget if Home is pressed', () => { layout.titles[1].focus(); simulate(layout.titles[1], 'keydown', { key: 'Home' }); expect(panel.events).to.contain('keydown'); expect(document.activeElement).to.be.equal(layout.titles[0]); }); it('should focus on the last widget if End is pressed', () => { layout.titles[1].focus(); simulate(layout.titles[1], 'keydown', { key: 'End' }); expect(panel.events).to.contain('keydown'); expect(document.activeElement).to.be.equal(layout.titles[2]); }); }); }); describe('#onBeforeAttach()', () => { it('should attach a click listener to the node', () => { let panel = new LogAccordionPanel(); Widget.attach(panel, document.body); simulate(panel.node, 'click'); expect(panel.events).to.contain('click'); panel.dispose(); }); it('should attach a keydown listener to the node', () => { let panel = new LogAccordionPanel(); Widget.attach(panel, document.body); simulate(panel.node, 'keydown'); expect(panel.events).to.contain('keydown'); panel.dispose(); }); }); describe('#onAfterDetach()', () => { it('should remove click listener', () => { let panel = new LogAccordionPanel(); Widget.attach(panel, document.body); simulate(panel.node, 'click'); expect(panel.events).to.contain('click'); Widget.detach(panel); panel.events = []; simulate(panel.node, 'click'); expect(panel.events).to.not.contain('click'); }); it('should remove keydown listener', () => { let panel = new LogAccordionPanel(); Widget.attach(panel, document.body); simulate(panel.node, 'keydown'); expect(panel.events).to.contain('keydown'); Widget.detach(panel); panel.events = []; simulate(panel.node, 'keydown'); expect(panel.events).to.not.contain('keydown'); }); }); describe('.Renderer()', () => { describe('.defaultRenderer', () => { it('should be an instance of `Renderer`', () => { expect(AccordionPanel.defaultRenderer).to.be.an.instanceof( AccordionPanel.Renderer ); }); }); describe('#constructor', () => { it('should create a section title', () => { const renderer = new AccordionPanel.Renderer(); expect( renderer.createSectionTitle( new Title({ owner: new Widget() }) ) ).to.be.instanceOf(HTMLElement); }); it('should have a section title selector', () => { const renderer = new AccordionPanel.Renderer(); expect(renderer.titleClassName).to.be.equal( 'lm-AccordionPanel-title' ); }); }); }); }); }); lumino-2021.12.13/packages/widgets/tests/src/boxengine.spec.ts000066400000000000000000000273621415564225700240670ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { expect } from 'chai'; import { BoxEngine, BoxSizer } from '@lumino/widgets'; function createSizers(n: number): BoxSizer[] { let sizers: BoxSizer[] = []; for (let i = 0; i < n; ++i) { sizers.push(new BoxSizer()); } return sizers; } describe('@lumino/widgets', () => { describe('BoxSizer', () => { describe('#constructor()', () => { it('should accept no arguments', () => { let sizer = new BoxSizer(); expect(sizer).to.be.an.instanceof(BoxSizer); }); }); describe('#sizeHint', () => { it('should default to `0`', () => { let sizer = new BoxSizer(); expect(sizer.sizeHint).to.equal(0); }); it('should be writable', () => { let sizer = new BoxSizer(); sizer.sizeHint = 42; expect(sizer.sizeHint).to.equal(42); }); }); describe('#minSize', () => { it('should default to `0`', () => { let sizer = new BoxSizer(); expect(sizer.minSize).to.equal(0); }); it('should be writable', () => { let sizer = new BoxSizer(); sizer.minSize = 42; expect(sizer.minSize).to.equal(42); }); }); describe('#maxSize', () => { it('should default to `Infinity`', () => { let sizer = new BoxSizer(); expect(sizer.maxSize).to.equal(Infinity); }); it('should be writable', () => { let sizer = new BoxSizer(); sizer.maxSize = 42; expect(sizer.maxSize).to.equal(42); }); }); describe('#stretch', () => { it('should default to `1`', () => { let sizer = new BoxSizer(); expect(sizer.stretch).to.equal(1); }); it('should be writable', () => { let sizer = new BoxSizer(); sizer.stretch = 42; expect(sizer.stretch).to.equal(42); }); }); describe('#size', () => { it('should be the computed output', () => { let sizer = new BoxSizer(); expect(typeof sizer.size).to.equal('number'); }); it('should be writable', () => { let sizer = new BoxSizer(); sizer.size = 42; expect(sizer.size).to.equal(42); }); }); }); describe('BoxEngine', () => { describe('calc()', () => { it('should handle an empty sizers array', () => { expect(() => BoxEngine.calc([], 100)).to.not.throw(Error); }); it('should obey the min sizes', () => { let sizers = createSizers(4); sizers[0].minSize = 10; sizers[1].minSize = 20; sizers[2].minSize = 30; sizers[3].minSize = 40; BoxEngine.calc(sizers, 0); expect(sizers[0].size).to.equal(10); expect(sizers[1].size).to.equal(20); expect(sizers[2].size).to.equal(30); expect(sizers[3].size).to.equal(40); }); it('should obey the max sizes', () => { let sizers = createSizers(4); sizers[0].maxSize = 10; sizers[1].maxSize = 20; sizers[2].maxSize = 30; sizers[3].maxSize = 40; BoxEngine.calc(sizers, 500); expect(sizers[0].size).to.equal(10); expect(sizers[1].size).to.equal(20); expect(sizers[2].size).to.equal(30); expect(sizers[3].size).to.equal(40); }); it('should handle negative layout space', () => { let sizers = createSizers(4); sizers[0].minSize = 10; sizers[1].minSize = 20; sizers[2].minSize = 30; BoxEngine.calc(sizers, -500); expect(sizers[0].size).to.equal(10); expect(sizers[1].size).to.equal(20); expect(sizers[2].size).to.equal(30); expect(sizers[3].size).to.equal(0); }); it('should handle infinite layout space', () => { let sizers = createSizers(4); sizers[0].maxSize = 10; sizers[1].maxSize = 20; sizers[2].maxSize = 30; BoxEngine.calc(sizers, Infinity); expect(sizers[0].size).to.equal(10); expect(sizers[1].size).to.equal(20); expect(sizers[2].size).to.equal(30); expect(sizers[3].size).to.equal(Infinity); }); it('should maintain the size hints if possible', () => { let sizers = createSizers(4); sizers[0].sizeHint = 40; sizers[1].sizeHint = 50; sizers[2].sizeHint = 60; sizers[3].sizeHint = 70; BoxEngine.calc(sizers, 220); expect(sizers[0].size).to.equal(40); expect(sizers[1].size).to.equal(50); expect(sizers[2].size).to.equal(60); expect(sizers[3].size).to.equal(70); }); it('should fairly distribute negative space', () => { let sizers = createSizers(4); sizers[0].sizeHint = 40; sizers[1].sizeHint = 50; sizers[2].sizeHint = 60; sizers[3].sizeHint = 70; BoxEngine.calc(sizers, 200); expect(sizers[0].size).to.equal(35); expect(sizers[1].size).to.equal(45); expect(sizers[2].size).to.equal(55); expect(sizers[3].size).to.equal(65); }); it('should fairly distribute positive space', () => { let sizers = createSizers(4); sizers[0].sizeHint = 40; sizers[1].sizeHint = 50; sizers[2].sizeHint = 60; sizers[3].sizeHint = 70; BoxEngine.calc(sizers, 240); expect(sizers[0].size).to.equal(45); expect(sizers[1].size).to.equal(55); expect(sizers[2].size).to.equal(65); expect(sizers[3].size).to.equal(75); }); it('should be callable multiple times for the same sizers', () => { let sizers = createSizers(4); sizers[0].sizeHint = 40; sizers[1].sizeHint = 50; sizers[2].sizeHint = 60; sizers[3].sizeHint = 70; BoxEngine.calc(sizers, 240); expect(sizers[0].size).to.equal(45); expect(sizers[1].size).to.equal(55); expect(sizers[2].size).to.equal(65); expect(sizers[3].size).to.equal(75); BoxEngine.calc(sizers, 280); expect(sizers[0].size).to.equal(55); expect(sizers[1].size).to.equal(65); expect(sizers[2].size).to.equal(75); expect(sizers[3].size).to.equal(85); BoxEngine.calc(sizers, 200); expect(sizers[0].size).to.equal(35); expect(sizers[1].size).to.equal(45); expect(sizers[2].size).to.equal(55); expect(sizers[3].size).to.equal(65); }); it('should distribute negative space according to stretch factors', () => { let sizers = createSizers(2); sizers[0].sizeHint = 60; sizers[1].sizeHint = 60; sizers[0].stretch = 2; sizers[1].stretch = 4; BoxEngine.calc(sizers, 120); expect(sizers[0].size).to.equal(60); expect(sizers[1].size).to.equal(60); BoxEngine.calc(sizers, 60); expect(sizers[0].size).to.equal(40); expect(sizers[1].size).to.equal(20); }); it('should distribute positive space according to stretch factors', () => { let sizers = createSizers(2); sizers[0].sizeHint = 60; sizers[1].sizeHint = 60; sizers[0].stretch = 2; sizers[1].stretch = 4; BoxEngine.calc(sizers, 120); expect(sizers[0].size).to.equal(60); expect(sizers[1].size).to.equal(60); BoxEngine.calc(sizers, 240); expect(sizers[0].size).to.equal(100); expect(sizers[1].size).to.equal(140); }); it('should not shrink non-stretchable sizers', () => { let sizers = createSizers(4); sizers[0].sizeHint = 20; sizers[1].sizeHint = 40; sizers[2].sizeHint = 60; sizers[3].sizeHint = 80; sizers[0].stretch = 0; sizers[2].stretch = 0; BoxEngine.calc(sizers, 160); expect(sizers[0].size).to.equal(20); expect(sizers[1].size).to.equal(20); expect(sizers[2].size).to.equal(60); expect(sizers[3].size).to.equal(60); }); it('should not expand non-stretchable sizers', () => { let sizers = createSizers(4); sizers[0].sizeHint = 20; sizers[1].sizeHint = 40; sizers[2].sizeHint = 60; sizers[3].sizeHint = 80; sizers[0].stretch = 0; sizers[2].stretch = 0; BoxEngine.calc(sizers, 260); expect(sizers[0].size).to.equal(20); expect(sizers[1].size).to.equal(70); expect(sizers[2].size).to.equal(60); expect(sizers[3].size).to.equal(110); }); it('should shrink non-stretchable sizers if required', () => { let sizers = createSizers(4); sizers[0].sizeHint = 20; sizers[1].sizeHint = 40; sizers[2].sizeHint = 60; sizers[3].sizeHint = 80; sizers[0].stretch = 0; sizers[2].stretch = 0; sizers[1].minSize = 20; sizers[2].minSize = 55; sizers[3].minSize = 60; BoxEngine.calc(sizers, 140); expect(sizers[0].size).to.equal(5); expect(sizers[1].size).to.equal(20); expect(sizers[2].size).to.equal(55); expect(sizers[3].size).to.equal(60); }); it('should expand non-stretchable sizers if required', () => { let sizers = createSizers(4); sizers[0].sizeHint = 20; sizers[1].sizeHint = 40; sizers[2].sizeHint = 60; sizers[3].sizeHint = 80; sizers[0].stretch = 0; sizers[2].stretch = 0; sizers[1].maxSize = 60; sizers[2].maxSize = 70; sizers[3].maxSize = 100; BoxEngine.calc(sizers, 280); expect(sizers[0].size).to.equal(50); expect(sizers[1].size).to.equal(60); expect(sizers[2].size).to.equal(70); expect(sizers[3].size).to.equal(100); }); }); describe('adjust()', () => { it('should adjust a sizer by a positive delta', () => { let sizers = createSizers(5); sizers[0].sizeHint = 50; sizers[1].sizeHint = 50; sizers[2].sizeHint = 50; sizers[3].sizeHint = 50; sizers[4].sizeHint = 50; sizers[2].maxSize = 60; sizers[3].minSize = 40; BoxEngine.calc(sizers, 250); expect(sizers[0].size).to.equal(50); expect(sizers[1].size).to.equal(50); expect(sizers[2].size).to.equal(50); expect(sizers[3].size).to.equal(50); expect(sizers[3].size).to.equal(50); BoxEngine.adjust(sizers, 2, 30); expect(sizers[0].sizeHint).to.equal(50); expect(sizers[1].sizeHint).to.equal(70); expect(sizers[2].sizeHint).to.equal(60); expect(sizers[3].sizeHint).to.equal(40); expect(sizers[4].sizeHint).to.equal(30); }); it('should adjust a sizer by a negative delta', () => { let sizers = createSizers(5); sizers[0].sizeHint = 50; sizers[1].sizeHint = 50; sizers[2].sizeHint = 50; sizers[3].sizeHint = 50; sizers[4].sizeHint = 50; sizers[1].minSize = 40; sizers[2].minSize = 40; BoxEngine.calc(sizers, 250); expect(sizers[0].size).to.equal(50); expect(sizers[1].size).to.equal(50); expect(sizers[2].size).to.equal(50); expect(sizers[3].size).to.equal(50); expect(sizers[3].size).to.equal(50); BoxEngine.adjust(sizers, 2, -30); expect(sizers[0].sizeHint).to.equal(40); expect(sizers[1].sizeHint).to.equal(40); expect(sizers[2].sizeHint).to.equal(40); expect(sizers[3].sizeHint).to.equal(80); expect(sizers[4].sizeHint).to.equal(50); }); }); }); }); lumino-2021.12.13/packages/widgets/tests/src/boxlayout.spec.ts000066400000000000000000000436341415564225700241370ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { expect } from 'chai'; import { each, every } from '@lumino/algorithm'; import { Message, MessageLoop } from '@lumino/messaging'; import { BoxLayout, Widget } from '@lumino/widgets'; class LogBoxLayout extends BoxLayout { methods: string[] = []; protected init(): void { super.init(); this.methods.push('init'); } protected attachWidget(index: number, widget: Widget): void { super.attachWidget(index, widget); this.methods.push('attachWidget'); } protected moveWidget( fromIndex: number, toIndex: number, widget: Widget ): void { super.moveWidget(fromIndex, toIndex, widget); this.methods.push('moveWidget'); } protected detachWidget(index: number, widget: Widget): void { super.detachWidget(index, widget); this.methods.push('detachWidget'); } protected onAfterShow(msg: Message): void { super.onAfterShow(msg); this.methods.push('onAfterShow'); } protected onAfterAttach(msg: Message): void { super.onAfterAttach(msg); this.methods.push('onAfterAttach'); } protected onChildShown(msg: Widget.ChildMessage): void { super.onChildShown(msg); this.methods.push('onChildShown'); } protected onChildHidden(msg: Widget.ChildMessage): void { super.onChildHidden(msg); this.methods.push('onChildHidden'); } protected onResize(msg: Widget.ResizeMessage): void { super.onResize(msg); this.methods.push('onResize'); } protected onUpdateRequest(msg: Message): void { super.onUpdateRequest(msg); this.methods.push('onUpdateRequest'); } protected onFitRequest(msg: Message): void { super.onFitRequest(msg); this.methods.push('onFitRequest'); } } class LogWidget extends Widget { methods: string[] = []; protected onAfterAttach(msg: Message): void { super.onAfterAttach(msg); this.methods.push('onAfterAttach'); } protected onBeforeDetach(msg: Message): void { super.onBeforeDetach(msg); this.methods.push('onBeforeDetach'); } protected onAfterShow(msg: Message): void { super.onAfterShow(msg); this.methods.push('onAfterShow'); } protected onUpdateRequest(msg: Message): void { super.onUpdateRequest(msg); this.methods.push('onUpdateRequest'); } } describe('@lumino/widgets', () => { describe('BoxLayout', () => { describe('constructor()', () => { it('should take no arguments', () => { let layout = new BoxLayout(); expect(layout).to.be.an.instanceof(BoxLayout); }); it('should accept options', () => { let layout = new BoxLayout({ direction: 'bottom-to-top', spacing: 10 }); expect(layout.direction).to.equal('bottom-to-top'); expect(layout.spacing).to.equal(10); }); }); describe('#direction', () => { it('should default to `"top-to-bottom"`', () => { let layout = new BoxLayout(); expect(layout.direction).to.equal('top-to-bottom'); }); it('should set the layout direction for the box layout', () => { let layout = new BoxLayout(); layout.direction = 'left-to-right'; expect(layout.direction).to.equal('left-to-right'); }); it('should set the direction attribute of the parent widget', () => { let parent = new Widget(); let layout = new BoxLayout(); parent.layout = layout; layout.direction = 'top-to-bottom'; expect(parent.node.getAttribute('data-direction')).to.equal( 'top-to-bottom' ); layout.direction = 'bottom-to-top'; expect(parent.node.getAttribute('data-direction')).to.equal( 'bottom-to-top' ); layout.direction = 'left-to-right'; expect(parent.node.getAttribute('data-direction')).to.equal( 'left-to-right' ); layout.direction = 'right-to-left'; expect(parent.node.getAttribute('data-direction')).to.equal( 'right-to-left' ); }); it('should post a fit request to the parent widget', done => { let parent = new Widget(); let layout = new LogBoxLayout(); parent.layout = layout; layout.direction = 'right-to-left'; requestAnimationFrame(() => { expect(layout.methods).to.contain('onFitRequest'); done(); }); }); it('should be a no-op if the value does not change', done => { let parent = new Widget(); let layout = new LogBoxLayout(); parent.layout = layout; layout.direction = 'top-to-bottom'; requestAnimationFrame(() => { expect(layout.methods).to.not.contain('onFitRequest'); done(); }); }); }); describe('#spacing', () => { it('should default to `4`', () => { let layout = new BoxLayout(); expect(layout.spacing).to.equal(4); }); it('should set the inter-element spacing for the box panel', () => { let layout = new BoxLayout(); layout.spacing = 8; expect(layout.spacing).to.equal(8); }); it('should post a fit request to the parent widget', done => { let parent = new Widget(); let layout = new LogBoxLayout(); parent.layout = layout; layout.spacing = 8; requestAnimationFrame(() => { expect(layout.methods).to.contain('onFitRequest'); done(); }); }); it('should be a no-op if the value does not change', done => { let parent = new Widget(); let layout = new LogBoxLayout(); parent.layout = layout; layout.spacing = 4; requestAnimationFrame(() => { expect(layout.methods).to.not.contain('onFitRequest'); done(); }); }); }); describe('#init()', () => { it('should set the direction attribute on the parent widget', () => { let parent = new Widget(); let layout = new LogBoxLayout(); parent.layout = layout; expect(parent.node.getAttribute('data-direction')).to.equal( 'top-to-bottom' ); expect(layout.methods).to.contain('init'); parent.dispose(); }); it('should attach the child widgets', () => { let parent = new Widget(); let layout = new LogBoxLayout(); let widgets = [new Widget(), new Widget(), new Widget()]; each(widgets, w => { layout.addWidget(w); }); parent.layout = layout; expect(every(widgets, w => w.parent === parent)); expect(layout.methods).to.contain('attachWidget'); parent.dispose(); }); }); describe('#attachWidget()', () => { it("should attach a widget to the parent's DOM node", () => { let panel = new Widget(); let layout = new LogBoxLayout(); let widget = new Widget(); panel.layout = layout; layout.addWidget(widget); layout.addWidget(widget); expect(layout.methods).to.contain('attachWidget'); expect(panel.node.contains(widget.node)).to.equal(true); panel.dispose(); }); it("should send an `'after-attach'` message if the parent is attached", () => { let panel = new Widget(); let layout = new LogBoxLayout(); let widget = new LogWidget(); panel.layout = layout; Widget.attach(panel, document.body); layout.addWidget(widget); expect(layout.methods).to.contain('attachWidget'); expect(widget.methods).to.contain('onAfterAttach'); panel.dispose(); }); it('should post a layout request for the parent widget', done => { let panel = new Widget(); let layout = new LogBoxLayout(); panel.layout = layout; layout.addWidget(new Widget()); requestAnimationFrame(() => { expect(layout.methods).to.contain('onFitRequest'); panel.dispose(); done(); }); }); }); describe('#moveWidget()', () => { it('should post an update request for the parent widget', done => { let panel = new Widget(); let layout = new LogBoxLayout(); panel.layout = layout; layout.addWidget(new Widget()); let widget = new Widget(); layout.addWidget(widget); layout.insertWidget(0, widget); expect(layout.methods).to.contain('moveWidget'); requestAnimationFrame(() => { expect(layout.methods).to.contain('onUpdateRequest'); panel.dispose(); done(); }); }); }); describe('#detachWidget()', () => { it("should detach a widget from the parent's DOM node", () => { let panel = new Widget(); let layout = new LogBoxLayout(); let widget = new Widget(); panel.layout = layout; layout.addWidget(widget); layout.removeWidget(widget); expect(layout.methods).to.contain('detachWidget'); expect(panel.node.contains(widget.node)).to.equal(false); panel.dispose(); }); it("should send a `'before-detach'` message if the parent is attached", () => { let panel = new Widget(); let layout = new LogBoxLayout(); panel.layout = layout; let widget = new LogWidget(); Widget.attach(panel, document.body); layout.addWidget(widget); layout.removeWidget(widget); expect(widget.methods).to.contain('onBeforeDetach'); panel.dispose(); }); it('should post a layout request for the parent widget', done => { let panel = new Widget(); let layout = new LogBoxLayout(); let widget = new Widget(); panel.layout = layout; layout.addWidget(widget); requestAnimationFrame(() => { expect(layout.methods).to.contain('onFitRequest'); layout.removeWidget(widget); layout.methods = []; requestAnimationFrame(() => { expect(layout.methods).to.contain('onFitRequest'); panel.dispose(); done(); }); }); }); }); describe('#onAfterShow()', () => { it('should post an update request to the parent', done => { let parent = new LogWidget(); let layout = new LogBoxLayout(); parent.layout = layout; Widget.attach(parent, document.body); parent.hide(); parent.show(); expect(parent.methods).to.contain('onAfterShow'); expect(layout.methods).to.contain('onAfterShow'); requestAnimationFrame(() => { expect(parent.methods).to.contain('onUpdateRequest'); parent.dispose(); done(); }); }); it('should send an `after-show` message to non-hidden child widgets', () => { let parent = new LogWidget(); let layout = new LogBoxLayout(); parent.layout = layout; let widgets = [new LogWidget(), new LogWidget(), new LogWidget()]; let hiddenWidgets = [new LogWidget(), new LogWidget()]; each(widgets, w => { layout.addWidget(w); }); each(hiddenWidgets, w => { layout.addWidget(w); }); each(hiddenWidgets, w => { w.hide(); }); Widget.attach(parent, document.body); parent.layout = layout; parent.hide(); parent.show(); expect(every(widgets, w => w.methods.indexOf('after-show') !== -1)); expect( every(hiddenWidgets, w => w.methods.indexOf('after-show') === -1) ); expect(parent.methods).to.contain('onAfterShow'); expect(layout.methods).to.contain('onAfterShow'); parent.dispose(); }); }); describe('#onAfterAttach()', () => { it('should post a fit request to the parent', done => { let parent = new LogWidget(); let layout = new LogBoxLayout(); parent.layout = layout; Widget.attach(parent, document.body); expect(parent.methods).to.contain('onAfterAttach'); expect(layout.methods).to.contain('onAfterAttach'); requestAnimationFrame(() => { expect(layout.methods).to.contain('onFitRequest'); parent.dispose(); done(); }); }); it('should send `after-attach` to all child widgets', () => { let parent = new LogWidget(); let layout = new LogBoxLayout(); parent.layout = layout; let widgets = [new LogWidget(), new LogWidget(), new LogWidget()]; each(widgets, w => { layout.addWidget(w); }); Widget.attach(parent, document.body); expect(parent.methods).to.contain('onAfterAttach'); expect(layout.methods).to.contain('onAfterAttach'); expect(every(widgets, w => w.methods.indexOf('onAfterAttach') !== -1)); parent.dispose(); }); }); describe('#onChildShown()', () => { it('should post or send a fit request to the parent', done => { let parent = new LogWidget(); let layout = new LogBoxLayout(); parent.layout = layout; let widgets = [new LogWidget(), new LogWidget(), new LogWidget()]; widgets[0].hide(); each(widgets, w => { layout.addWidget(w); }); Widget.attach(parent, document.body); widgets[0].show(); expect(layout.methods).to.contain('onChildShown'); requestAnimationFrame(() => { expect(layout.methods).to.contain('onFitRequest'); parent.dispose(); done(); }); }); }); describe('#onChildHidden()', () => { it('should post a fit request to the parent', done => { let parent = new LogWidget(); let layout = new LogBoxLayout(); parent.layout = layout; let widgets = [new LogWidget(), new LogWidget(), new LogWidget()]; each(widgets, w => { layout.addWidget(w); }); Widget.attach(parent, document.body); widgets[0].hide(); expect(layout.methods).to.contain('onChildHidden'); requestAnimationFrame(() => { expect(layout.methods).to.contain('onFitRequest'); parent.dispose(); done(); }); }); }); describe('#onResize()', () => { it('should be called when a resize event is sent to the parent', () => { let parent = new LogWidget(); let layout = new LogBoxLayout(); parent.layout = layout; let widgets = [new LogWidget(), new LogWidget(), new LogWidget()]; each(widgets, w => { layout.addWidget(w); }); Widget.attach(parent, document.body); MessageLoop.sendMessage(parent, Widget.ResizeMessage.UnknownSize); expect(layout.methods).to.contain('onResize'); parent.dispose(); }); it('should be a no-op if the parent is hidden', () => { let parent = new LogWidget(); let layout = new LogBoxLayout(); parent.layout = layout; Widget.attach(parent, document.body); parent.hide(); MessageLoop.sendMessage(parent, Widget.ResizeMessage.UnknownSize); expect(layout.methods).to.contain('onResize'); parent.dispose(); }); }); describe('#onUpdateRequest()', () => { it('should be called when the parent is updated', () => { let parent = new Widget(); let layout = new LogBoxLayout(); parent.layout = layout; MessageLoop.sendMessage(parent, Widget.Msg.UpdateRequest); expect(layout.methods).to.contain('onUpdateRequest'); }); }); describe('#onFitRequest()', () => { it('should be called when the parent fit is requested', () => { let parent = new Widget(); let layout = new LogBoxLayout(); parent.layout = layout; MessageLoop.sendMessage(parent, Widget.Msg.FitRequest); expect(layout.methods).to.contain('onFitRequest'); }); }); describe('.getStretch()', () => { it('should get the box panel stretch factor for the given widget', () => { let widget = new Widget(); expect(BoxLayout.getStretch(widget)).to.equal(0); }); }); describe('.setStretch()', () => { it('should set the box panel stretch factor for the given widget', () => { let widget = new Widget(); BoxLayout.setStretch(widget, 8); expect(BoxLayout.getStretch(widget)).to.equal(8); }); it("should post a fit request to the widget's parent", done => { let parent = new Widget(); let widget = new Widget(); let layout = new LogBoxLayout(); parent.layout = layout; layout.addWidget(widget); BoxLayout.setStretch(widget, 8); requestAnimationFrame(() => { expect(layout.methods).to.contain('onFitRequest'); done(); }); }); }); describe('.getSizeBasis()', () => { it('should get the box panel size basis for the given widget', () => { let widget = new Widget(); expect(BoxLayout.getSizeBasis(widget)).to.equal(0); }); }); describe('.setSizeBasis()', () => { it('should set the box panel size basis for the given widget', () => { let widget = new Widget(); BoxLayout.setSizeBasis(widget, 8); expect(BoxLayout.getSizeBasis(widget)).to.equal(8); }); it("should post a fit request to the widget's parent", done => { let parent = new Widget(); let widget = new Widget(); let layout = new LogBoxLayout(); parent.layout = layout; layout.addWidget(widget); BoxLayout.setSizeBasis(widget, 8); requestAnimationFrame(() => { expect(layout.methods).to.contain('onFitRequest'); done(); }); }); }); }); }); lumino-2021.12.13/packages/widgets/tests/src/boxpanel.spec.ts000066400000000000000000000102321415564225700237050ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { expect } from 'chai'; import { BoxLayout, BoxPanel, Widget } from '@lumino/widgets'; describe('@lumino/widgets', () => { describe('BoxPanel', () => { describe('#constructor()', () => { it('should take no arguments', () => { let panel = new BoxPanel(); expect(panel).to.be.an.instanceof(BoxPanel); }); it('should accept options', () => { let panel = new BoxPanel({ direction: 'bottom-to-top', spacing: 10 }); expect(panel.direction).to.equal('bottom-to-top'); expect(panel.spacing).to.equal(10); }); it('should accept a layout option', () => { let layout = new BoxLayout(); let panel = new BoxPanel({ layout }); expect(panel.layout).to.equal(layout); }); it('should ignore other options if a layout is given', () => { let layout = new BoxLayout(); let panel = new BoxPanel({ layout, direction: 'bottom-to-top', spacing: 10 }); expect(panel.layout).to.equal(layout); expect(panel.direction).to.equal('top-to-bottom'); expect(panel.spacing).to.equal(4); }); it('should add the `lm-BoxPanel` class', () => { let panel = new BoxPanel(); expect(panel.hasClass('lm-BoxPanel')).to.equal(true); }); }); describe('#direction', () => { it('should default to `"top-to-bottom"`', () => { let panel = new BoxPanel(); expect(panel.direction).to.equal('top-to-bottom'); }); it('should set the layout direction for the box panel', () => { let panel = new BoxPanel(); panel.direction = 'left-to-right'; expect(panel.direction).to.equal('left-to-right'); }); }); describe('#spacing', () => { it('should default to `4`', () => { let panel = new BoxPanel(); expect(panel.spacing).to.equal(4); }); it('should set the inter-element spacing for the box panel', () => { let panel = new BoxPanel(); panel.spacing = 8; expect(panel.spacing).to.equal(8); }); }); describe('#onChildAdded()', () => { it('should add the child class to a child added to the panel', () => { let panel = new BoxPanel(); let widget = new Widget(); panel.addWidget(widget); expect(widget.hasClass('lm-BoxPanel-child')).to.equal(true); }); }); describe('#onChildRemoved()', () => { it('should remove the child class from a child removed from the panel', () => { let panel = new BoxPanel(); let widget = new Widget(); panel.addWidget(widget); widget.parent = null; expect(widget.hasClass('lm-BoxPanel-child')).to.equal(false); }); }); describe('.getStretch()', () => { it('should get the box panel stretch factor for the given widget', () => { let widget = new Widget(); expect(BoxPanel.getStretch(widget)).to.equal(0); }); }); describe('.setStretch()', () => { it('should set the box panel stretch factor for the given widget', () => { let widget = new Widget(); BoxPanel.setStretch(widget, 8); expect(BoxPanel.getStretch(widget)).to.equal(8); }); }); describe('.getSizeBasis()', () => { it('should get the box panel size basis for the given widget', () => { let widget = new Widget(); expect(BoxPanel.getSizeBasis(widget)).to.equal(0); }); }); describe('.setSizeBasis()', () => { it('should set the box panel size basis for the given widget', () => { let widget = new Widget(); BoxPanel.setSizeBasis(widget, 8); expect(BoxPanel.getSizeBasis(widget)).to.equal(8); }); }); }); }); lumino-2021.12.13/packages/widgets/tests/src/commandpalette.spec.ts000066400000000000000000000657271415564225700251150ustar00rootroot00000000000000/* eslint-disable @typescript-eslint/no-empty-function */ // Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { expect } from 'chai'; import { simulate } from 'simulate-event'; import { CommandRegistry } from '@lumino/commands'; import { Platform } from '@lumino/domutils'; import { MessageLoop } from '@lumino/messaging'; import { h, VirtualDOM } from '@lumino/virtualdom'; import { CommandPalette, Widget } from '@lumino/widgets'; class LogPalette extends CommandPalette { events: string[] = []; handleEvent(event: Event): void { super.handleEvent(event); this.events.push(event.type); } } const defaultOptions: CommandPalette.IItemOptions = { command: 'test', category: 'Test Category', args: { foo: 'bar' }, rank: 42 }; describe('@lumino/widgets', () => { let commands: CommandRegistry; let palette: CommandPalette; beforeEach(() => { commands = new CommandRegistry(); palette = new CommandPalette({ commands }); }); afterEach(() => { palette.dispose(); }); describe('CommandPalette', () => { describe('#constructor()', () => { it('should accept command palette instantiation options', () => { expect(palette).to.be.an.instanceof(CommandPalette); expect(palette.node.classList.contains('lm-CommandPalette')).to.equal( true ); }); }); describe('#dispose()', () => { it('should dispose of the resources held by the command palette', () => { palette.addItem(defaultOptions); palette.dispose(); expect(palette.items.length).to.equal(0); expect(palette.isDisposed).to.equal(true); }); }); describe('#commands', () => { it('should get the command registry for the command palette', () => { expect(palette.commands).to.equal(commands); }); }); describe('#renderer', () => { it('should default to the default renderer', () => { expect(palette.renderer).to.equal(CommandPalette.defaultRenderer); }); }); describe('#searchNode', () => { it('should return the search node of a command palette', () => { expect( palette.searchNode.classList.contains('lm-CommandPalette-search') ).to.equal(true); }); }); describe('#inputNode', () => { it('should return the input node of a command palette', () => { expect( palette.inputNode.classList.contains('lm-CommandPalette-input') ).to.equal(true); }); }); describe('#contentNode', () => { it('should return the content node of a command palette', () => { expect( palette.contentNode.classList.contains('lm-CommandPalette-content') ).to.equal(true); }); }); describe('#items', () => { it('should be a read-only array of the palette items', () => { expect(palette.items.length).to.equal(0); palette.addItem(defaultOptions); expect(palette.items.length).to.equal(1); expect(palette.items[0].command).to.equal('test'); }); }); describe('#addItems()', () => { it('should add items to a command palette using options', () => { const item = { command: 'test2', category: 'Test Category', args: { foo: 'bar' }, rank: 100 }; expect(palette.items.length).to.equal(0); palette.addItems([defaultOptions, item]); expect(palette.items.length).to.equal(2); expect(palette.items[0].command).to.equal('test'); expect(palette.items[1].command).to.equal('test2'); }); }); describe('#addItem()', () => { it('should add an item to a command palette using options', () => { expect(palette.items.length).to.equal(0); palette.addItem(defaultOptions); expect(palette.items.length).to.equal(1); expect(palette.items[0].command).to.equal('test'); }); context('CommandPalette.IItem', () => { describe('#command', () => { it('should return the command name of a command item', () => { let item = palette.addItem(defaultOptions); expect(item.command).to.equal('test'); }); }); describe('#args', () => { it('should return the args of a command item', () => { let item = palette.addItem(defaultOptions); expect(item.args).to.deep.equal(defaultOptions.args); }); it('should default to an empty object', () => { let item = palette.addItem({ command: 'test', category: 'test' }); expect(item.args).to.deep.equal({}); }); }); describe('#category', () => { it('should return the category of a command item', () => { let item = palette.addItem(defaultOptions); expect(item.category).to.equal(defaultOptions.category); }); }); describe('#rank', () => { it('should return the rank of a command item', () => { let item = palette.addItem(defaultOptions); expect(item.rank).to.deep.equal(defaultOptions.rank); }); it('should default to `Infinity`', () => { let item = palette.addItem({ command: 'test', category: 'test' }); expect(item.rank).to.equal(Infinity); }); }); describe('#label', () => { it('should return the label of a command item', () => { let label = 'test label'; commands.addCommand('test', { execute: () => {}, label }); let item = palette.addItem(defaultOptions); expect(item.label).to.equal(label); }); }); describe('#caption', () => { it('should return the caption of a command item', () => { let caption = 'test caption'; commands.addCommand('test', { execute: () => {}, caption }); let item = palette.addItem(defaultOptions); expect(item.caption).to.equal(caption); }); }); describe('#className', () => { it('should return the class name of a command item', () => { let className = 'testClass'; commands.addCommand('test', { execute: () => {}, className }); let item = palette.addItem(defaultOptions); expect(item.className).to.equal(className); }); }); describe('#isEnabled', () => { it('should return whether a command item is enabled', () => { let called = false; commands.addCommand('test', { execute: () => {}, isEnabled: () => { called = true; return false; } }); let item = palette.addItem(defaultOptions); expect(called).to.equal(false); expect(item.isEnabled).to.equal(false); expect(called).to.equal(true); }); }); describe('#isToggled', () => { it('should return whether a command item is toggled', () => { let called = false; commands.addCommand('test', { execute: () => {}, isToggled: () => { called = true; return true; } }); let item = palette.addItem(defaultOptions); expect(called).to.equal(false); expect(item.isToggled).to.equal(true); expect(called).to.equal(true); }); }); describe('#isVisible', () => { it('should return whether a command item is visible', () => { let called = false; commands.addCommand('test', { execute: () => {}, isVisible: () => { called = true; return false; } }); let item = palette.addItem(defaultOptions); expect(called).to.equal(false); expect(item.isVisible).to.equal(false); expect(called).to.equal(true); }); }); describe('#keyBinding', () => { it('should return the key binding of a command item', () => { commands.addKeyBinding({ keys: ['Ctrl A'], selector: 'body', command: 'test', args: defaultOptions.args }); let item = palette.addItem(defaultOptions); expect(item.keyBinding!.keys).to.deep.equal(['Ctrl A']); }); }); }); }); describe('#removeItem()', () => { it('should remove an item from a command palette by item', () => { expect(palette.items.length).to.equal(0); let item = palette.addItem(defaultOptions); expect(palette.items.length).to.equal(1); palette.removeItem(item); expect(palette.items.length).to.equal(0); }); }); describe('#removeItemAt()', () => { it('should remove an item from a command palette by index', () => { expect(palette.items.length).to.equal(0); palette.addItem(defaultOptions); expect(palette.items.length).to.equal(1); palette.removeItemAt(0); expect(palette.items.length).to.equal(0); }); }); describe('#clearItems()', () => { it('should remove all items from a command palette', () => { expect(palette.items.length).to.equal(0); palette.addItem({ command: 'test', category: 'one' }); palette.addItem({ command: 'test', category: 'two' }); expect(palette.items.length).to.equal(2); palette.clearItems(); expect(palette.items.length).to.equal(0); }); }); describe('#refresh()', () => { it('should schedule a refresh of the search items', () => { commands.addCommand('test', { execute: () => {}, label: 'test' }); palette.addItem(defaultOptions); MessageLoop.flush(); let content = palette.contentNode; let itemClass = '.lm-CommandPalette-item'; let items = () => content.querySelectorAll(itemClass); expect(items()).to.have.length(1); palette.inputNode.value = 'x'; palette.refresh(); MessageLoop.flush(); expect(items()).to.have.length(0); }); }); describe('#handleEvent()', () => { it('should handle click, keydown, and input events', () => { let palette = new LogPalette({ commands }); Widget.attach(palette, document.body); ['click', 'keydown', 'input'].forEach(type => { simulate(palette.node, type); expect(palette.events).to.contain(type); }); palette.dispose(); }); context('click', () => { it('should trigger a command when its item is clicked', () => { let called = false; commands.addCommand('test', { execute: () => (called = true) }); palette.addItem(defaultOptions); Widget.attach(palette, document.body); MessageLoop.flush(); let node = palette.contentNode.querySelector( '.lm-CommandPalette-item' )!; simulate(node, 'click'); expect(called).to.equal(true); }); it('should ignore the event if it is not a left click', () => { let called = false; commands.addCommand('test', { execute: () => (called = true) }); palette.addItem(defaultOptions); Widget.attach(palette, document.body); MessageLoop.flush(); let node = palette.contentNode.querySelector( '.lm-CommandPalette-item' )!; simulate(node, 'click', { button: 1 }); expect(called).to.equal(false); }); }); context('keydown', () => { it('should navigate down if down arrow is pressed', () => { commands.addCommand('test', { execute: () => {} }); let content = palette.contentNode; palette.addItem(defaultOptions); Widget.attach(palette, document.body); MessageLoop.flush(); let node = content.querySelector('.lm-mod-active'); expect(node).to.equal(null); simulate(palette.node, 'keydown', { keyCode: 40 }); // Down arrow MessageLoop.flush(); node = content.querySelector('.lm-CommandPalette-item.lm-mod-active'); expect(node).to.not.equal(null); }); it('should navigate up if up arrow is pressed', () => { commands.addCommand('test', { execute: () => {} }); let content = palette.contentNode; palette.addItem(defaultOptions); Widget.attach(palette, document.body); MessageLoop.flush(); let node = content.querySelector('.lm-mod-active'); expect(node).to.equal(null); simulate(palette.node, 'keydown', { keyCode: 38 }); // Up arrow MessageLoop.flush(); node = content.querySelector('.lm-CommandPalette-item.lm-mod-active'); expect(node).to.not.equal(null); }); it('should ignore if modifier keys are pressed', () => { let called = false; commands.addCommand('test', { execute: () => (called = true) }); let content = palette.contentNode; palette.addItem(defaultOptions); Widget.attach(palette, document.body); MessageLoop.flush(); let node = content.querySelector('.lm-mod-active'); expect(node).to.equal(null); ['altKey', 'ctrlKey', 'shiftKey', 'metaKey'].forEach(key => { let options: any = { keyCode: 38 }; options[key] = true; simulate(palette.node, 'keydown', options); node = content.querySelector( '.lm-CommandPalette-item.lm-mod-active' ); expect(node).to.equal(null); }); expect(called).to.be.false; }); it('should trigger active item if enter is pressed', () => { let called = false; commands.addCommand('test', { execute: () => (called = true) }); let content = palette.contentNode; palette.addItem(defaultOptions); Widget.attach(palette, document.body); MessageLoop.flush(); expect(content.querySelector('.lm-mod-active')).to.equal(null); simulate(palette.node, 'keydown', { keyCode: 40 }); // Down arrow simulate(palette.node, 'keydown', { keyCode: 13 }); // Enter expect(called).to.equal(true); }); }); context('input', () => { it('should filter the list of visible items', () => { ['A', 'B', 'C', 'D', 'E'].forEach(name => { commands.addCommand(name, { execute: () => {}, label: name }); palette.addItem({ command: name, category: 'test' }); }); Widget.attach(palette, document.body); MessageLoop.flush(); let content = palette.contentNode; let itemClass = '.lm-CommandPalette-item'; let items = () => content.querySelectorAll(itemClass); expect(items()).to.have.length(5); palette.inputNode.value = 'A'; palette.refresh(); MessageLoop.flush(); expect(items()).to.have.length(1); }); it('should filter by both text and category', () => { let categories = ['Z', 'Y']; let names = [ ['A1', 'B2', 'C3', 'D4', 'E5'], ['F1', 'G2', 'H3', 'I4', 'J5'] ]; names.forEach((values, index) => { values.forEach(command => { palette.addItem({ command, category: categories[index] }); commands.addCommand(command, { execute: () => {}, label: command }); }); }); Widget.attach(palette, document.body); MessageLoop.flush(); let headers = () => palette.node.querySelectorAll('.lm-CommandPalette-header'); let items = () => palette.node.querySelectorAll('.lm-CommandPalette-item'); let input = (value: string) => { palette.inputNode.value = value; palette.refresh(); MessageLoop.flush(); }; expect(items()).to.have.length(10); input(`${categories[1]}`); // Category match expect(items()).to.have.length(5); input(`${names[1][0]}`); // Label match expect(items()).to.have.length(1); input(`${categories[1]} B`); // No match expect(items()).to.have.length(0); input(`${categories[1]} I`); // Category and text match expect(items()).to.have.length(1); input('1'); // Multi-category match expect(headers()).to.have.length(2); expect(items()).to.have.length(2); }); }); }); describe('.Renderer', () => { let renderer = new CommandPalette.Renderer(); let item: CommandPalette.IItem = null!; let enabledFlag = true; let toggledFlag = false; beforeEach(() => { enabledFlag = true; toggledFlag = false; commands.addCommand('test', { label: 'Test Command', caption: 'A simple test command', className: 'testClass', isEnabled: () => enabledFlag, isToggled: () => toggledFlag, execute: () => {} }); commands.addKeyBinding({ command: 'test', keys: ['Ctrl A'], selector: 'body' }); item = palette.addItem({ command: 'test', category: 'Test Category' }); }); describe('#renderHeader()', () => { it('should render a header node for the palette', () => { let vNode = renderer.renderHeader({ category: 'Test Category', indices: null }); let node = VirtualDOM.realize(vNode); expect(node.classList.contains('lm-CommandPalette-header')).to.equal( true ); expect(node.innerHTML).to.equal('Test Category'); }); it('should mark the matching indices', () => { let vNode = renderer.renderHeader({ category: 'Test Category', indices: [1, 2, 6, 7, 8] }); let node = VirtualDOM.realize(vNode); expect(node.classList.contains('lm-CommandPalette-header')).to.equal( true ); expect(node.innerHTML).to.equal( 'Test Category' ); }); }); describe('#renderItem()', () => { it('should render an item node for the palette', () => { let vNode = renderer.renderItem({ item, indices: null, active: false }); let node = VirtualDOM.realize(vNode); expect(node.classList.contains('lm-CommandPalette-item')).to.equal( true ); expect(node.classList.contains('lm-mod-disabled')).to.equal(false); expect(node.classList.contains('lm-mod-toggled')).to.equal(false); expect(node.classList.contains('lm-mod-active')).to.equal(false); expect(node.classList.contains('testClass')).to.equal(true); expect(node.getAttribute('data-command')).to.equal('test'); expect( node.querySelector('.lm-CommandPalette-itemShortcut') ).to.not.equal(null); expect( node.querySelector('.lm-CommandPalette-itemLabel') ).to.not.equal(null); expect( node.querySelector('.lm-CommandPalette-itemCaption') ).to.not.equal(null); }); it('should handle the disabled item state', () => { enabledFlag = false; let vNode = renderer.renderItem({ item, indices: null, active: false }); let node = VirtualDOM.realize(vNode); expect(node.classList.contains('lm-mod-disabled')).to.equal(true); }); it('should handle the toggled item state', () => { toggledFlag = true; let vNode = renderer.renderItem({ item, indices: null, active: false }); let node = VirtualDOM.realize(vNode); expect(node.classList.contains('lm-mod-toggled')).to.equal(true); }); it('should handle the active state', () => { let vNode = renderer.renderItem({ item, indices: null, active: true }); let node = VirtualDOM.realize(vNode); expect(node.classList.contains('lm-mod-active')).to.equal(true); }); }); describe('#renderEmptyMessage()', () => { it('should render an empty message node for the palette', () => { let vNode = renderer.renderEmptyMessage({ query: 'foo' }); let node = VirtualDOM.realize(vNode); expect( node.classList.contains('lm-CommandPalette-emptyMessage') ).to.equal(true); expect(node.innerHTML).to.equal("No commands found that match 'foo'"); }); }); describe('#renderItemShortcut()', () => { it('should render an item shortcut node', () => { let vNode = renderer.renderItemShortcut({ item, indices: null, active: false }); let node = VirtualDOM.realize(vNode); expect( node.classList.contains('lm-CommandPalette-itemShortcut') ).to.equal(true); if (Platform.IS_MAC) { expect(node.innerHTML).to.equal('\u2303 A'); } else { expect(node.innerHTML).to.equal('Ctrl+A'); } }); }); describe('#renderItemLabel()', () => { it('should render an item label node', () => { let vNode = renderer.renderItemLabel({ item, indices: [1, 2, 3], active: false }); let node = VirtualDOM.realize(vNode); expect( node.classList.contains('lm-CommandPalette-itemLabel') ).to.equal(true); expect(node.innerHTML).to.equal('Test Command'); }); }); describe('#renderItemCaption()', () => { it('should render an item caption node', () => { let vNode = renderer.renderItemCaption({ item, indices: null, active: false }); let node = VirtualDOM.realize(vNode); expect( node.classList.contains('lm-CommandPalette-itemCaption') ).to.equal(true); expect(node.innerHTML).to.equal('A simple test command'); }); }); describe('#createItemClass()', () => { it('should create the full class name for the item node', () => { let name = renderer.createItemClass({ item, indices: null, active: false }); let expected = 'lm-CommandPalette-item testClass'; /* */ expected = 'lm-CommandPalette-item p-CommandPalette-item testClass'; /* */ expect(name).to.equal(expected); }); it('should handle the boolean states', () => { enabledFlag = false; toggledFlag = true; let name = renderer.createItemClass({ item, indices: null, active: true }); let expected = 'lm-CommandPalette-item lm-mod-disabled lm-mod-toggled lm-mod-active testClass'; /* */ expected = 'lm-CommandPalette-item p-CommandPalette-item lm-mod-disabled p-mod-disabled lm-mod-toggled p-mod-toggled lm-mod-active p-mod-active testClass'; /* */ expect(name).to.equal(expected); }); }); describe('#createItemDataset()', () => { it('should create the item dataset', () => { let dataset = renderer.createItemDataset({ item, indices: null, active: false }); expect(dataset).to.deep.equal({ command: 'test' }); }); }); describe('#formatHeader()', () => { it('should format unmatched header content', () => { let child1 = renderer.formatHeader({ category: 'Test Category', indices: null }); let child2 = renderer.formatHeader({ category: 'Test Category', indices: [] }); expect(child1).to.equal('Test Category'); expect(child2).to.equal('Test Category'); }); it('should format matched header content', () => { let child = renderer.formatHeader({ category: 'Test Category', indices: [1, 2, 6, 7, 8] }); let node = VirtualDOM.realize(h.div(child)); expect(node.innerHTML).to.equal( 'Test Category' ); }); }); describe('#formatEmptyMessage()', () => { it('should format the empty message text', () => { let child = renderer.formatEmptyMessage({ query: 'foo' }); expect(child).to.equal("No commands found that match 'foo'"); }); }); describe('#formatItemShortcut()', () => { it('should format the item shortcut text', () => { let child = renderer.formatItemShortcut({ item, indices: null, active: false }); if (Platform.IS_MAC) { expect(child).to.equal('\u2303 A'); } else { expect(child).to.equal('Ctrl+A'); } }); }); describe('#formatItemLabel()', () => { it('should format unmatched label content', () => { let child1 = renderer.formatItemLabel({ item, indices: null, active: false }); let child2 = renderer.formatItemLabel({ item, indices: [], active: false }); expect(child1).to.equal('Test Command'); expect(child2).to.equal('Test Command'); }); it('should format matched label content', () => { let child = renderer.formatItemLabel({ item, indices: [1, 2, 3], active: false }); let node = VirtualDOM.realize(h.div(child)); expect(node.innerHTML).to.equal('Test Command'); }); }); describe('#formatItemCaption()', () => { it('should format the item caption text', () => { let child = renderer.formatItemCaption({ item, indices: null, active: false }); expect(child).to.equal('A simple test command'); }); }); }); }); }); lumino-2021.12.13/packages/widgets/tests/src/contextmenu.spec.ts000066400000000000000000000130131415564225700244460ustar00rootroot00000000000000/* eslint-disable @typescript-eslint/no-empty-function */ // Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { expect } from 'chai'; // import { simulate } from 'simulate-event'; import { CommandRegistry } from '@lumino/commands'; import { JSONObject } from '@lumino/coreutils'; import { ContextMenu } from '@lumino/widgets'; describe('@lumino/widgets', () => { let commands = new CommandRegistry(); before(() => { commands.addCommand('test-1', { execute: (args: JSONObject) => {}, label: 'Test 1 Label' }); commands.addCommand('test-2', { execute: (args: JSONObject) => {}, label: 'Test 2 Label' }); commands.addCommand('test-3', { execute: (args: JSONObject) => {}, label: 'Test 3 Label' }); commands.addCommand('test-4', { execute: (args: JSONObject) => {}, label: 'Test 4 Label' }); }); describe('ContextMenu', () => { describe('#open', () => { let menu: ContextMenu; const CLASSNAME = 'menu-1'; function addItems(menu: ContextMenu) { menu.addItem({ command: 'test-1', selector: `.${CLASSNAME}`, rank: 20 }); menu.addItem({ command: 'test-2', selector: `.${CLASSNAME}`, rank: 10 }); menu.addItem({ command: 'test-3', selector: `div.${CLASSNAME}`, rank: 30 }); menu.addItem({ command: 'test-4', selector: '.menu-2', rank: 1 }); menu.addItem({ command: 'test-5', selector: 'body', rank: 15 }); } afterEach(() => { menu && menu.menu.dispose(); }); it('should show items matching selector, grouped and ordered by selector and rank', () => { const target = document.createElement('div'); target.className = CLASSNAME; document.body.appendChild(target); menu = new ContextMenu({ commands }); addItems(menu); const bb = target.getBoundingClientRect() as DOMRect; menu.open({ target, currentTarget: document.body, clientX: bb.x, clientY: bb.y } as any); expect(menu.menu.items).to.have.length(4); expect(menu.menu.items[0].command).to.equal('test-3'); expect(menu.menu.items[1].command).to.equal('test-2'); expect(menu.menu.items[2].command).to.equal('test-1'); expect(menu.menu.items[3].command).to.equal('test-5'); }); it('should show items matching selector, grouped and ordered only by rank', () => { const target = document.createElement('div'); target.className = CLASSNAME; document.body.appendChild(target); menu = new ContextMenu({ commands, sortBySelector: false }); addItems(menu); const bb = target.getBoundingClientRect() as DOMRect; menu.open({ target, currentTarget: document.body, clientX: bb.x, clientY: bb.y } as any); expect(menu.menu.items).to.have.length(4); expect(menu.menu.items[0].command).to.equal('test-2'); expect(menu.menu.items[1].command).to.equal('test-1'); expect(menu.menu.items[2].command).to.equal('test-3'); expect(menu.menu.items[3].command).to.equal('test-5'); }); it('should show items matching selector, ungrouped and ordered by selector and rank', () => { const target = document.createElement('div'); target.className = CLASSNAME; document.body.appendChild(target); menu = new ContextMenu({ commands, groupByTarget: false, sortBySelector: false }); addItems(menu); const bb = target.getBoundingClientRect() as DOMRect; menu.open({ target, currentTarget: document.body, clientX: bb.x, clientY: bb.y } as any); expect(menu.menu.items).to.have.length(4); expect(menu.menu.items[1].command).to.equal('test-5'); expect(menu.menu.items[0].command).to.equal('test-2'); expect(menu.menu.items[2].command).to.equal('test-1'); expect(menu.menu.items[3].command).to.equal('test-3'); }); it('should show items matching selector, ungrouped and ordered only by rank', () => { const target = document.createElement('div'); target.className = CLASSNAME; document.body.appendChild(target); menu = new ContextMenu({ commands, groupByTarget: false, sortBySelector: false }); addItems(menu); const bb = target.getBoundingClientRect() as DOMRect; menu.open({ target, currentTarget: document.body, clientX: bb.x, clientY: bb.y } as any); expect(menu.menu.items).to.have.length(4); expect(menu.menu.items[0].command).to.equal('test-2'); expect(menu.menu.items[1].command).to.equal('test-5'); expect(menu.menu.items[2].command).to.equal('test-1'); expect(menu.menu.items[3].command).to.equal('test-3'); }); }); }); }); lumino-2021.12.13/packages/widgets/tests/src/docklayout.spec.ts000066400000000000000000000007041415564225700242560ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ lumino-2021.12.13/packages/widgets/tests/src/dockpanel.spec.ts000066400000000000000000000102371415564225700240420ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { expect } from 'chai'; import { each } from '@lumino/algorithm'; import { DockPanel, TabBar, Widget } from '@lumino/widgets'; describe('@lumino/widgets', () => { describe('DockPanel', () => { describe('#constructor()', () => { it('should construct a new dock panel and take no arguments', () => { let panel = new DockPanel(); expect(panel).to.be.an.instanceof(DockPanel); }); it('should accept options', () => { let renderer = Object.create(TabBar.defaultRenderer); let panel = new DockPanel({ tabsMovable: true, renderer, tabsConstrained: true }); each(panel.tabBars(), tabBar => { expect(tabBar.tabsMovable).to.equal(true); }); each(panel.tabBars(), tabBar => { expect(tabBar.renderer).to.equal(renderer); }); }); it('should not have tabs constrained by default', () => { let panel = new DockPanel(); expect(panel.tabsConstrained).to.equal(false); }); it('should add a `lm-DockPanel` class', () => { let panel = new DockPanel(); expect(panel.hasClass('lm-DockPanel')).to.equal(true); }); }); describe('#dispose()', () => { it('should dispose of the resources held by the widget', () => { let panel = new DockPanel(); panel.addWidget(new Widget()); panel.dispose(); expect(panel.isDisposed).to.equal(true); panel.dispose(); expect(panel.isDisposed).to.equal(true); }); }); describe('hiddenMode', () => { let panel: DockPanel; let widgets: Widget[] = []; beforeEach(() => { panel = new DockPanel(); // Create two stacked widgets widgets.push(new Widget()); panel.addWidget(widgets[0]); widgets.push(new Widget()); panel.addWidget(widgets[1], { mode: 'tab-after' }); }); afterEach(() => { panel.dispose(); }); it("should be 'display' mode by default", () => { expect(panel.hiddenMode).to.equal(Widget.HiddenMode.Display); }); it("should switch to 'scale'", () => { widgets[0].hiddenMode = Widget.HiddenMode.Scale; panel.hiddenMode = Widget.HiddenMode.Scale; expect(widgets[0].hiddenMode).to.equal(Widget.HiddenMode.Scale); expect(widgets[1].hiddenMode).to.equal(Widget.HiddenMode.Scale); }); it("should switch to 'display'", () => { widgets[0].hiddenMode = Widget.HiddenMode.Scale; panel.hiddenMode = Widget.HiddenMode.Scale; panel.hiddenMode = Widget.HiddenMode.Display; expect(widgets[0].hiddenMode).to.equal(Widget.HiddenMode.Display); expect(widgets[1].hiddenMode).to.equal(Widget.HiddenMode.Display); }); it("should not set 'scale' if only one widget", () => { panel.layout!.removeWidget(widgets[1]); panel.hiddenMode = Widget.HiddenMode.Scale; expect(widgets[0].hiddenMode).to.equal(Widget.HiddenMode.Display); }); }); describe('#tabsMovable', () => { it('should get whether tabs are movable', () => { let panel = new DockPanel(); expect(panel.tabsMovable).to.equal(true); }); it('should set tabsMovable of all tabs', () => { let panel = new DockPanel(); let w1 = new Widget(); let w2 = new Widget(); panel.addWidget(w1); panel.addWidget(w2, { mode: 'split-right', ref: w1 }); each(panel.tabBars(), tabBar => { expect(tabBar.tabsMovable).to.equal(true); }); panel.tabsMovable = false; each(panel.tabBars(), tabBar => { expect(tabBar.tabsMovable).to.equal(false); }); }); }); }); }); lumino-2021.12.13/packages/widgets/tests/src/focustracker.spec.ts000066400000000000000000000315211415564225700245740ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { expect } from 'chai'; import { simulate } from 'simulate-event'; import { Platform } from '@lumino/domutils'; import { FocusTracker, Widget } from '@lumino/widgets'; describe('@lumino/widgets', () => { let _trackers: FocusTracker[] = []; let _widgets: Widget[] = []; function createTracker(): FocusTracker { let tracker = new FocusTracker(); _trackers.push(tracker); return tracker; } function createWidget(): Widget { let widget = new Widget(); widget.node.tabIndex = -1; Widget.attach(widget, document.body); _widgets.push(widget); return widget; } function focusWidget(widget: Widget): void { widget.node.focus(); if (Platform.IS_IE) { // Ensure we get a synchronous event on IE for testing. simulate(widget.node, 'focus'); } } function blurWidget(widget: Widget): void { widget.node.blur(); if (Platform.IS_IE) { // Ensure we get a synchronous event on IE for testing. simulate(widget.node, 'blur'); } } afterEach(() => { while (_trackers.length > 0) { _trackers.pop()!.dispose(); } while (_widgets.length > 0) { _widgets.pop()!.dispose(); } }); describe('FocusTracker', () => { describe('#constructor()', () => { it('should create a FocusTracker', () => { let tracker = new FocusTracker(); expect(tracker).to.be.an.instanceof(FocusTracker); }); }); describe('#dispose()', () => { it('should dispose of the resources held by the tracker', () => { let tracker = new FocusTracker(); tracker.add(createWidget()); tracker.dispose(); expect(tracker.widgets.length).to.equal(0); }); it('should be a no-op if already disposed', () => { let tracker = new FocusTracker(); tracker.dispose(); tracker.dispose(); expect(tracker.isDisposed).to.equal(true); }); }); describe('#currentChanged', () => { it('should be emitted when the current widget has changed', () => { let tracker = createTracker(); let widget0 = createWidget(); let widget1 = createWidget(); tracker.add(widget0); tracker.add(widget1); focusWidget(widget0); let emitArgs: FocusTracker.IChangedArgs | null = null; tracker.currentChanged.connect((sender, args) => { emitArgs = args; }); focusWidget(widget1); expect(emitArgs).to.not.equal(null); expect(emitArgs!.oldValue).to.equal(widget0); expect(emitArgs!.newValue).to.equal(widget1); }); it('should not be emitted when the current widget does not change', () => { let tracker = createTracker(); let widget = createWidget(); focusWidget(widget); tracker.add(widget); let emitArgs: FocusTracker.IChangedArgs | null = null; tracker.currentChanged.connect((sender, args) => { emitArgs = args; }); blurWidget(widget); focusWidget(widget); expect(emitArgs).to.equal(null); }); }); describe('#activeChanged', () => { it('should be emitted when the active widget has changed', () => { let tracker = createTracker(); let widget0 = createWidget(); let widget1 = createWidget(); tracker.add(widget0); tracker.add(widget1); focusWidget(widget0); let emitArgs: FocusTracker.IChangedArgs | null = null; tracker.activeChanged.connect((sender, args) => { emitArgs = args; }); focusWidget(widget1); expect(emitArgs).to.not.equal(null); expect(emitArgs!.oldValue).to.equal(widget0); expect(emitArgs!.newValue).to.equal(widget1); }); it('should be emitted when the active widget is set to null', () => { let tracker = createTracker(); let widget = createWidget(); focusWidget(widget); tracker.add(widget); let emitArgs: FocusTracker.IChangedArgs | null = null; tracker.activeChanged.connect((sender, args) => { emitArgs = args; }); blurWidget(widget); expect(emitArgs).to.not.equal(null); expect(emitArgs!.oldValue).to.equal(widget); expect(emitArgs!.newValue).to.equal(null); }); }); describe('#isDisposed', () => { it('should indicate whether the tracker is disposed', () => { let tracker = new FocusTracker(); expect(tracker.isDisposed).to.equal(false); tracker.dispose(); expect(tracker.isDisposed).to.equal(true); }); }); describe('#currentWidget', () => { it('should get the current widget in the tracker', () => { let tracker = createTracker(); let widget = createWidget(); focusWidget(widget); tracker.add(widget); expect(tracker.currentWidget).to.equal(widget); }); it('should not be updated when the current widget loses focus', () => { let tracker = createTracker(); let widget = createWidget(); focusWidget(widget); tracker.add(widget); expect(tracker.currentWidget).to.equal(widget); blurWidget(widget); expect(tracker.currentWidget).to.equal(widget); }); it('should be set to the widget that gained focus', () => { let tracker = createTracker(); let widget0 = createWidget(); let widget1 = createWidget(); focusWidget(widget0); tracker.add(widget0); tracker.add(widget1); expect(tracker.currentWidget).to.equal(widget0); focusWidget(widget1); expect(tracker.currentWidget).to.equal(widget1); }); it('should revert to the previous widget if the current widget is removed', () => { let tracker = createTracker(); let widget0 = createWidget(); let widget1 = createWidget(); focusWidget(widget0); tracker.add(widget0); tracker.add(widget1); focusWidget(widget1); expect(tracker.currentWidget).to.equal(widget1); widget1.dispose(); expect(tracker.currentWidget).to.equal(widget0); }); it('should be `null` if there is no current widget', () => { let tracker = createTracker(); expect(tracker.currentWidget).to.equal(null); let widget = createWidget(); focusWidget(widget); tracker.add(widget); expect(tracker.currentWidget).to.equal(widget); widget.dispose(); expect(tracker.currentWidget).to.equal(null); }); }); describe('#activeWidget', () => { it('should get the active widget in the tracker', () => { let tracker = createTracker(); let widget = createWidget(); focusWidget(widget); tracker.add(widget); expect(tracker.activeWidget).to.equal(widget); }); it('should be set to `null` when the active widget loses focus', () => { let tracker = createTracker(); let widget = createWidget(); focusWidget(widget); tracker.add(widget); expect(tracker.activeWidget).to.equal(widget); blurWidget(widget); expect(tracker.activeWidget).to.equal(null); }); it('should be set to the widget that gained focus', () => { let tracker = createTracker(); let widget0 = createWidget(); let widget1 = createWidget(); focusWidget(widget0); tracker.add(widget0); tracker.add(widget1); expect(tracker.activeWidget).to.equal(widget0); focusWidget(widget1); expect(tracker.activeWidget).to.equal(widget1); }); it('should be `null` if there is no active widget', () => { let tracker = createTracker(); expect(tracker.currentWidget).to.equal(null); let widget = createWidget(); focusWidget(widget); tracker.add(widget); expect(tracker.activeWidget).to.equal(widget); widget.dispose(); expect(tracker.activeWidget).to.equal(null); }); }); describe('#widgets', () => { it('should be a read-only sequence of the widgets being tracked', () => { let tracker = createTracker(); expect(tracker.widgets.length).to.equal(0); let widget = createWidget(); tracker.add(widget); expect(tracker.widgets.length).to.equal(1); expect(tracker.widgets[0]).to.equal(widget); }); }); describe('#focusNumber()', () => { it('should get the focus number for a particular widget in the tracker', () => { let tracker = createTracker(); let widget = createWidget(); focusWidget(widget); tracker.add(widget); expect(tracker.focusNumber(widget)).to.equal(0); }); it('should give the highest number for the currentWidget', () => { let tracker = createTracker(); let widget0 = createWidget(); let widget1 = createWidget(); focusWidget(widget0); tracker.add(widget0); tracker.add(widget1); focusWidget(widget1); expect(tracker.focusNumber(widget1)).to.equal(1); expect(tracker.focusNumber(widget0)).to.equal(0); focusWidget(widget0); expect(tracker.focusNumber(widget0)).to.equal(2); }); it('should start a widget with a focus number of `-1`', () => { let tracker = createTracker(); let widget = createWidget(); tracker.add(widget); expect(tracker.focusNumber(widget)).to.equal(-1); }); it('should update when a widget gains focus', () => { let tracker = createTracker(); let widget0 = createWidget(); let widget1 = createWidget(); focusWidget(widget0); tracker.add(widget0); tracker.add(widget1); focusWidget(widget1); expect(tracker.focusNumber(widget0)).to.equal(0); focusWidget(widget0); expect(tracker.focusNumber(widget0)).to.equal(2); }); }); describe('#has()', () => { it('should test whether the focus tracker contains a given widget', () => { let tracker = createTracker(); let widget = createWidget(); expect(tracker.has(widget)).to.equal(false); tracker.add(widget); expect(tracker.has(widget)).to.equal(true); }); }); describe('#add()', () => { it('should add a widget to the focus tracker', () => { let tracker = createTracker(); let widget = createWidget(); tracker.add(widget); expect(tracker.has(widget)).to.equal(true); }); it('should make the widget the currentWidget if focused', () => { let tracker = createTracker(); let widget = createWidget(); focusWidget(widget); tracker.add(widget); expect(tracker.currentWidget).to.equal(widget); }); it('should remove the widget from the tracker after it has been disposed', () => { let tracker = createTracker(); let widget = createWidget(); tracker.add(widget); widget.dispose(); expect(tracker.has(widget)).to.equal(false); }); it('should be a no-op if the widget is already tracked', () => { let tracker = createTracker(); let widget = createWidget(); tracker.add(widget); tracker.add(widget); expect(tracker.has(widget)).to.equal(true); }); }); describe('#remove()', () => { it('should remove a widget from the focus tracker', () => { let tracker = createTracker(); let widget = createWidget(); tracker.add(widget); tracker.remove(widget); expect(tracker.has(widget)).to.equal(false); }); it('should set the currentWidget to the previous one if the widget is the currentWidget', () => { let tracker = createTracker(); let widget0 = createWidget(); let widget1 = createWidget(); let widget2 = createWidget(); focusWidget(widget0); tracker.add(widget0); tracker.add(widget1); tracker.add(widget2); focusWidget(widget1); focusWidget(widget2); tracker.remove(widget2); expect(tracker.currentWidget).to.equal(widget1); }); it('should be a no-op if the widget is not tracked', () => { let tracker = createTracker(); let widget = createWidget(); tracker.remove(widget); expect(tracker.has(widget)).to.equal(false); }); }); }); }); lumino-2021.12.13/packages/widgets/tests/src/index.spec.ts000066400000000000000000000022321415564225700232050ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import 'es6-promise/auto'; // polyfill Promise on IE import '@lumino/widgets/style/index.css'; import './accordionlayout.spec'; import './accordionpanel.spec'; import './boxengine.spec'; import './boxlayout.spec'; import './boxpanel.spec'; import './commandpalette.spec'; import './contextmenu.spec'; import './docklayout.spec'; import './dockpanel.spec'; import './focustracker.spec'; import './layout.spec'; import './menu.spec'; import './menubar.spec'; import './panel.spec'; import './panellayout.spec'; import './splitlayout.spec'; import './splitpanel.spec'; import './stackedlayout.spec'; import './stackedpanel.spec'; import './tabbar.spec'; import './tabpanel.spec'; import './title.spec'; import './widget.spec'; lumino-2021.12.13/packages/widgets/tests/src/layout.spec.ts000066400000000000000000000272131415564225700234210ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { expect } from 'chai'; import { ArrayExt, every, IIterator, iter, toArray } from '@lumino/algorithm'; import { Message, MessageLoop } from '@lumino/messaging'; import { Layout, Widget } from '@lumino/widgets'; import { LogWidget } from './widget.spec'; class LogLayout extends Layout { methods: string[] = []; widgets = [new LogWidget(), new LogWidget()]; dispose(): void { while (this.widgets.length !== 0) { this.widgets.pop()!.dispose(); } super.dispose(); } iter(): IIterator { return iter(this.widgets); } removeWidget(widget: Widget): void { this.methods.push('removeWidget'); ArrayExt.removeFirstOf(this.widgets, widget); } protected init(): void { this.methods.push('init'); super.init(); } protected onResize(msg: Widget.ResizeMessage): void { super.onResize(msg); this.methods.push('onResize'); } protected onUpdateRequest(msg: Message): void { super.onUpdateRequest(msg); this.methods.push('onUpdateRequest'); } protected onAfterAttach(msg: Message): void { super.onAfterAttach(msg); this.methods.push('onAfterAttach'); } protected onBeforeDetach(msg: Message): void { super.onBeforeDetach(msg); this.methods.push('onBeforeDetach'); } protected onAfterShow(msg: Message): void { super.onAfterShow(msg); this.methods.push('onAfterShow'); } protected onBeforeHide(msg: Message): void { super.onBeforeHide(msg); this.methods.push('onBeforeHide'); } protected onFitRequest(msg: Widget.ChildMessage): void { super.onFitRequest(msg); this.methods.push('onFitRequest'); } protected onChildShown(msg: Widget.ChildMessage): void { super.onChildShown(msg); this.methods.push('onChildShown'); } protected onChildHidden(msg: Widget.ChildMessage): void { super.onChildHidden(msg); this.methods.push('onChildHidden'); } } describe('@lumino/widgets', () => { describe('Layout', () => { describe('#iter()', () => { it('should create an iterator over the widgets in the layout', () => { let layout = new LogLayout(); expect(every(layout, child => child instanceof Widget)).to.equal(true); }); }); describe('#removeWidget()', () => { it("should be invoked when a child widget's `parent` property is set to `null`", () => { let parent = new Widget(); let layout = new LogLayout(); parent.layout = layout; layout.widgets[0].parent = null; expect(layout.methods).to.contain('removeWidget'); }); }); describe('#dispose()', () => { it('should dispose of the resource held by the layout', () => { let widget = new Widget(); let layout = new LogLayout(); widget.layout = layout; let children = toArray(widget.children()); layout.dispose(); expect(layout.parent).to.equal(null); expect(every(children, w => w.isDisposed)).to.equal(true); }); it('should be called automatically when the parent is disposed', () => { let widget = new Widget(); let layout = new LogLayout(); widget.layout = layout; widget.dispose(); expect(layout.parent).to.equal(null); expect(layout.isDisposed).to.equal(true); }); }); describe('#isDisposed', () => { it('should test whether the layout is disposed', () => { let layout = new LogLayout(); expect(layout.isDisposed).to.equal(false); layout.dispose(); expect(layout.isDisposed).to.equal(true); }); }); describe('#parent', () => { it('should get the parent widget of the layout', () => { let layout = new LogLayout(); let parent = new Widget(); parent.layout = layout; expect(layout.parent).to.equal(parent); }); it('should throw an error if set to `null`', () => { let layout = new LogLayout(); let parent = new Widget(); parent.layout = layout; expect(() => { layout.parent = null; }).to.throw(Error); }); it('should throw an error if set to a different value', () => { let layout = new LogLayout(); let parent = new Widget(); parent.layout = layout; expect(() => { layout.parent = new Widget(); }).to.throw(Error); }); it('should be a no-op if the parent is set to the same value', () => { let layout = new LogLayout(); let parent = new Widget(); parent.layout = layout; layout.parent = parent; expect(layout.parent).to.equal(parent); }); }); describe('#init()', () => { it('should be invoked when the layout is installed on its parent widget', () => { let widget = new Widget(); let layout = new LogLayout(); widget.layout = layout; expect(layout.methods).to.contain('init'); }); it('should reparent the child widgets', () => { let widget = new Widget(); let layout = new LogLayout(); widget.layout = layout; expect(every(layout, child => child.parent === widget)).to.equal(true); }); }); describe('#onResize()', () => { it('should be invoked on a `resize` message', () => { let layout = new LogLayout(); let parent = new Widget(); parent.layout = layout; MessageLoop.sendMessage(parent, Widget.ResizeMessage.UnknownSize); expect(layout.methods).to.contain('onResize'); }); it('should send a `resize` message to each of the widgets in the layout', () => { let layout = new LogLayout(); let parent = new Widget(); parent.layout = layout; MessageLoop.sendMessage(parent, Widget.ResizeMessage.UnknownSize); expect(layout.methods).to.contain('onResize'); expect(layout.widgets[0].methods).to.contain('onResize'); expect(layout.widgets[1].methods).to.contain('onResize'); }); }); describe('#onUpdateRequest()', () => { it('should be invoked on an `update-request` message', () => { let layout = new LogLayout(); let parent = new Widget(); parent.layout = layout; MessageLoop.sendMessage(parent, Widget.Msg.UpdateRequest); expect(layout.methods).to.contain('onUpdateRequest'); }); it('should send a `resize` message to each of the widgets in the layout', () => { let layout = new LogLayout(); let parent = new Widget(); parent.layout = layout; MessageLoop.sendMessage(parent, Widget.Msg.UpdateRequest); expect(layout.methods).to.contain('onUpdateRequest'); expect(layout.widgets[0].methods).to.contain('onResize'); expect(layout.widgets[1].methods).to.contain('onResize'); }); }); describe('#onAfterAttach()', () => { it('should be invoked on an `after-attach` message', () => { let layout = new LogLayout(); let parent = new Widget(); parent.layout = layout; MessageLoop.sendMessage(parent, Widget.Msg.AfterAttach); expect(layout.methods).to.contain('onAfterAttach'); }); it('should send an `after-attach` message to each of the widgets in the layout', () => { let layout = new LogLayout(); let parent = new Widget(); parent.layout = layout; MessageLoop.sendMessage(parent, Widget.Msg.AfterAttach); expect(layout.methods).to.contain('onAfterAttach'); expect(layout.widgets[0].methods).to.contain('onAfterAttach'); expect(layout.widgets[1].methods).to.contain('onAfterAttach'); }); }); describe('#onBeforeDetach()', () => { it('should be invoked on an `before-detach` message', () => { let layout = new LogLayout(); let parent = new Widget(); parent.layout = layout; MessageLoop.sendMessage(parent, Widget.Msg.BeforeDetach); expect(layout.methods).to.contain('onBeforeDetach'); }); it('should send a `before-detach` message to each of the widgets in the layout', () => { let layout = new LogLayout(); let parent = new Widget(); parent.layout = layout; MessageLoop.sendMessage(parent, Widget.Msg.BeforeDetach); expect(layout.methods).to.contain('onBeforeDetach'); expect(layout.widgets[0].methods).to.contain('onBeforeDetach'); expect(layout.widgets[1].methods).to.contain('onBeforeDetach'); }); }); describe('#onAfterShow()', () => { it('should be invoked on an `after-show` message', () => { let layout = new LogLayout(); let parent = new Widget(); parent.layout = layout; MessageLoop.sendMessage(parent, Widget.Msg.AfterShow); expect(layout.methods).to.contain('onAfterShow'); }); it('should send an `after-show` message to non hidden of the widgets in the layout', () => { let layout = new LogLayout(); let parent = new Widget(); parent.layout = layout; layout.widgets[0].hide(); MessageLoop.sendMessage(parent, Widget.Msg.AfterShow); expect(layout.methods).to.contain('onAfterShow'); expect(layout.widgets[0].methods).to.not.contain('onAfterShow'); expect(layout.widgets[1].methods).to.contain('onAfterShow'); }); }); describe('#onBeforeHide()', () => { it('should be invoked on a `before-hide` message', () => { let layout = new LogLayout(); let parent = new Widget(); parent.layout = layout; MessageLoop.sendMessage(parent, Widget.Msg.BeforeHide); expect(layout.methods).to.contain('onBeforeHide'); }); it('should send a `before-hide` message to non hidden of the widgets in the layout', () => { let layout = new LogLayout(); let parent = new Widget(); parent.layout = layout; layout.widgets[0].hide(); MessageLoop.sendMessage(parent, Widget.Msg.BeforeHide); expect(layout.methods).to.contain('onBeforeHide'); expect(layout.widgets[0].methods).to.not.contain('onBeforeHide'); expect(layout.widgets[1].methods).to.contain('onBeforeHide'); }); }); describe('#onFitRequest()', () => { it('should be invoked on an `fit-request` message', () => { let layout = new LogLayout(); let parent = new Widget(); parent.layout = layout; MessageLoop.sendMessage(parent, Widget.Msg.FitRequest); expect(layout.methods).to.contain('onFitRequest'); }); }); describe('#onChildShown()', () => { it('should be invoked on an `child-shown` message', () => { let layout = new LogLayout(); let parent = new Widget(); parent.layout = layout; let msg = new Widget.ChildMessage('child-shown', new Widget()); MessageLoop.sendMessage(parent, msg); expect(layout.methods).to.contain('onChildShown'); }); }); describe('#onChildHidden()', () => { it('should be invoked on an `child-hidden` message', () => { let layout = new LogLayout(); let parent = new Widget(); parent.layout = layout; let msg = new Widget.ChildMessage('child-hidden', new Widget()); MessageLoop.sendMessage(parent, msg); expect(layout.methods).to.contain('onChildHidden'); }); }); }); }); lumino-2021.12.13/packages/widgets/tests/src/menu.spec.ts000066400000000000000000001526701415564225700230560ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { expect } from 'chai'; import { simulate } from 'simulate-event'; import { CommandRegistry } from '@lumino/commands'; import { JSONObject } from '@lumino/coreutils'; import { Platform } from '@lumino/domutils'; import { Message } from '@lumino/messaging'; import { h, VirtualDOM } from '@lumino/virtualdom'; import { Menu, Widget } from '@lumino/widgets'; class LogMenu extends Menu { events: string[] = []; methods: string[] = []; handleEvent(event: Event): void { super.handleEvent(event); this.events.push(event.type); } protected onBeforeAttach(msg: Message): void { super.onBeforeAttach(msg); this.methods.push('onBeforeAttach'); } protected onAfterDetach(msg: Message): void { super.onAfterDetach(msg); this.methods.push('onAfterDetach'); } protected onActivateRequest(msg: Message): void { super.onActivateRequest(msg); this.methods.push('onActivateRequest'); } protected onUpdateRequest(msg: Message): void { super.onUpdateRequest(msg); this.methods.push('onUpdateRequest'); } protected onCloseRequest(msg: Message): void { super.onCloseRequest(msg); this.methods.push('onCloseRequest'); } } describe('@lumino/widgets', () => { let commands = new CommandRegistry(); let logMenu: LogMenu = null!; let menu: Menu = null!; let executed = ''; before(() => { commands.addCommand('test', { execute: (args: JSONObject) => { executed = 'test'; }, label: 'Test Label', icon: 'foo', caption: 'Test Caption', className: 'testClass', mnemonic: 0 }); commands.addCommand('test-toggled', { execute: (args: JSONObject) => { executed = 'test-toggled'; }, label: 'Test Toggled Label', icon: 'foo', className: 'testClass', isToggled: (args: JSONObject) => true, mnemonic: 6 }); commands.addCommand('test-disabled', { execute: (args: JSONObject) => { executed = 'test-disabled'; }, label: 'Test Disabled Label', icon: 'foo', className: 'testClass', isEnabled: (args: JSONObject) => false, mnemonic: 5 }); commands.addCommand('test-hidden', { execute: (args: JSONObject) => { executed = 'test-hidden'; }, label: 'Hidden Label', icon: 'foo', className: 'testClass', isVisible: (args: JSONObject) => false }); commands.addCommand('test-zenith', { execute: (args: JSONObject) => { executed = 'test-zenith'; }, label: 'Zenith Label', icon: 'foo', className: 'testClass' }); commands.addKeyBinding({ keys: ['Ctrl T'], selector: 'body', command: 'test' }); }); beforeEach(() => { executed = ''; menu = new Menu({ commands }); logMenu = new LogMenu({ commands }); }); afterEach(() => { menu.dispose(); logMenu.dispose(); }); describe('Menu', () => { describe('#constructor()', () => { it('should take options for initializing the menu', () => { let menu = new Menu({ commands }); expect(menu).to.be.an.instanceof(Menu); }); it('should add the `lm-Menu` class', () => { let menu = new Menu({ commands }); expect(menu.hasClass('lm-Menu')).to.equal(true); }); }); describe('#dispose()', () => { it('should dispose of the resources held by the menu', () => { menu.addItem({}); expect(menu.items.length).to.equal(1); menu.dispose(); expect(menu.items.length).to.equal(0); expect(menu.isDisposed).to.equal(true); }); }); describe('#aboutToClose', () => { it('should be emitted just before the menu is closed', () => { let called = false; menu.open(0, 0); menu.aboutToClose.connect((sender, args) => { called = true; }); menu.close(); expect(called).to.equal(true); }); it('should not be emitted if the menu is not attached', () => { let called = false; menu.open(0, 0); menu.aboutToClose.connect(() => { called = true; }); Widget.detach(menu); menu.close(); expect(called).to.equal(false); }); }); describe('menuRequested', () => { it('should be emitted when a left arrow key is pressed and a submenu cannot be opened or closed', () => { let called = false; menu.open(0, 0); menu.menuRequested.connect((sender, args) => { expect(args).to.equal('previous'); called = true; }); simulate(menu.node, 'keydown', { keyCode: 37 }); expect(called).to.equal(true); }); it('should be emitted when a right arrow key is pressed and a submenu cannot be opened or closed', () => { let called = false; menu.open(0, 0); menu.menuRequested.connect((sender, args) => { expect(args).to.equal('next'); called = true; }); simulate(menu.node, 'keydown', { keyCode: 39 }); expect(called).to.equal(true); }); it('should only be emitted for the root menu in a hierarchy', () => { let submenu = new Menu({ commands }); let item = menu.addItem({ type: 'submenu', submenu }); menu.open(0, 0); menu.activeItem = item; menu.triggerActiveItem(); let called = false; let submenuCalled = false; menu.menuRequested.connect((sender, args) => { expect(args).to.equal('next'); called = true; }); submenu.menuRequested.connect(() => { submenuCalled = true; }); simulate(submenu.node, 'keydown', { keyCode: 39 }); expect(called).to.equal(true); expect(submenuCalled).to.equal(false); }); }); describe('#commands', () => { it('should be the command registry for the menu', () => { expect(menu.commands).to.equal(commands); }); }); describe('#renderer', () => { it('should default to the default renderer', () => { expect(menu.renderer).to.equal(Menu.defaultRenderer); }); it('should be the renderer for the menu', () => { let renderer = Object.create(Menu.defaultRenderer); let menu = new Menu({ commands, renderer }); expect(menu.renderer).to.equal(renderer); }); }); describe('#parentMenu', () => { it('should get the parent menu of the menu', () => { let submenu = new Menu({ commands }); let item = menu.addItem({ type: 'submenu', submenu }); menu.open(0, 0); menu.activeItem = item; menu.triggerActiveItem(); expect(submenu.parentMenu).to.equal(menu); }); it('should be `null` if the menu is not an open submenu', () => { let submenu = new Menu({ commands }); menu.addItem({ type: 'submenu', submenu }); menu.open(0, 0); expect(submenu.parentMenu).to.equal(null); expect(menu.parentMenu).to.equal(null); }); }); describe('#childMenu', () => { it('should get the child menu of the menu', () => { let submenu = new Menu({ commands }); let item = menu.addItem({ type: 'submenu', submenu }); menu.open(0, 0); menu.activeItem = item; menu.triggerActiveItem(); expect(menu.childMenu).to.equal(submenu); }); it('should be `null` if the menu does not have an open submenu', () => { let submenu = new Menu({ commands }); menu.addItem({ type: 'submenu', submenu }); menu.open(0, 0); expect(menu.childMenu).to.equal(null); }); }); describe('#rootMenu', () => { it('should get the root menu of the menu hierarchy', () => { let submenu1 = new Menu({ commands }); let submenu2 = new Menu({ commands }); let item1 = menu.addItem({ type: 'submenu', submenu: submenu1 }); let item2 = submenu1.addItem({ type: 'submenu', submenu: submenu2 }); menu.open(0, 0); menu.activeItem = item1; menu.triggerActiveItem(); submenu1.activeItem = item2; submenu1.triggerActiveItem(); expect(submenu2.rootMenu).to.equal(menu); }); it('should be itself if the menu is not an open submenu', () => { let submenu1 = new Menu({ commands }); let submenu2 = new Menu({ commands }); menu.addItem({ type: 'submenu', submenu: submenu1 }); submenu1.addItem({ type: 'submenu', submenu: submenu2 }); menu.open(0, 0); expect(menu.rootMenu).to.equal(menu); expect(submenu1.rootMenu).to.equal(submenu1); expect(submenu2.rootMenu).to.equal(submenu2); }); }); describe('#leafMenu', () => { it('should get the leaf menu of the menu hierarchy', () => { let submenu1 = new Menu({ commands }); let submenu2 = new Menu({ commands }); let item1 = menu.addItem({ type: 'submenu', submenu: submenu1 }); let item2 = submenu1.addItem({ type: 'submenu', submenu: submenu2 }); menu.open(0, 0); menu.activeItem = item1; menu.triggerActiveItem(); submenu1.activeItem = item2; submenu1.triggerActiveItem(); expect(menu.leafMenu).to.equal(submenu2); }); it('should be itself if the menu does not have an open submenu', () => { let submenu1 = new Menu({ commands }); let submenu2 = new Menu({ commands }); menu.addItem({ type: 'submenu', submenu: submenu1 }); submenu1.addItem({ type: 'submenu', submenu: submenu2 }); menu.open(0, 0); expect(menu.leafMenu).to.equal(menu); expect(submenu1.leafMenu).to.equal(submenu1); expect(submenu2.leafMenu).to.equal(submenu2); }); }); describe('#contentNode', () => { it('should get the menu content node', () => { let content = menu.contentNode; expect(content.classList.contains('lm-Menu-content')).to.equal(true); }); }); describe('#activeItem', () => { it('should get the currently active menu item', () => { let item = menu.addItem({ command: 'test' }); menu.activeIndex = 0; expect(menu.activeItem).to.equal(item); }); it('should be `null` if no menu item is active', () => { expect(menu.activeItem).to.equal(null); menu.addItem({ command: 'test' }); expect(menu.activeItem).to.equal(null); }); it('should set the currently active menu item', () => { expect(menu.activeItem).to.equal(null); let item = (menu.activeItem = menu.addItem({ command: 'test' })); expect(menu.activeItem).to.equal(item); }); it('should set to `null` if the item cannot be activated', () => { expect(menu.activeItem).to.equal(null); menu.activeItem = menu.addItem({ command: 'test-disabled' }); expect(menu.activeItem).to.equal(null); }); }); describe('#activeIndex', () => { it('should get the index of the currently active menu item', () => { menu.activeItem = menu.addItem({ command: 'test' }); expect(menu.activeIndex).to.equal(0); }); it('should be `-1` if no menu item is active', () => { expect(menu.activeIndex).to.equal(-1); menu.addItem({ command: 'test' }); expect(menu.activeIndex).to.equal(-1); }); it('should set the currently active menu item index', () => { expect(menu.activeIndex).to.equal(-1); menu.addItem({ command: 'test' }); menu.activeIndex = 0; expect(menu.activeIndex).to.equal(0); }); it('should set to `-1` if the item cannot be activated', () => { menu.addItem({ command: 'test-disabled' }); menu.activeIndex = 0; expect(menu.activeIndex).to.equal(-1); }); }); describe('#items', () => { it('should be a read-only array of the menu items in the menu', () => { let item1 = menu.addItem({ command: 'foo' }); let item2 = menu.addItem({ command: 'bar' }); expect(menu.items).to.deep.equal([item1, item2]); }); }); describe('#activateNextItem()', () => { it('should activate the next selectable item in the menu', () => { menu.addItem({ command: 'test-disabled' }); menu.addItem({ command: 'test' }); menu.activateNextItem(); expect(menu.activeIndex).to.equal(1); }); it('should set the index to `-1` if no item is selectable', () => { menu.addItem({ command: 'test-disabled' }); menu.addItem({ type: 'separator' }); menu.activateNextItem(); expect(menu.activeIndex).to.equal(-1); }); }); describe('#activatePreviousItem()', () => { it('should activate the next selectable item in the menu', () => { menu.addItem({ command: 'test' }); menu.addItem({ command: 'test-disabled' }); menu.activatePreviousItem(); expect(menu.activeIndex).to.equal(0); }); it('should set the index to `-1` if no item is selectable', () => { menu.addItem({ command: 'test-disabled' }); menu.addItem({ type: 'separator' }); menu.activatePreviousItem(); expect(menu.activeIndex).to.equal(-1); }); }); describe('#triggerActiveItem()', () => { it('should execute a command if it is the active item', () => { menu.addItem({ command: 'test' }); menu.open(0, 0); menu.activeIndex = 0; menu.triggerActiveItem(); expect(executed).to.equal('test'); }); it('should open a submenu and activate the first item', () => { let submenu = new Menu({ commands }); submenu.addItem({ command: 'test' }); menu.addItem({ type: 'submenu', submenu }); menu.open(0, 0); menu.activeIndex = 0; menu.triggerActiveItem(); expect(submenu.parentMenu).to.equal(menu); expect(submenu.activeIndex).to.equal(0); }); it('should be a no-op if the menu is not attached', () => { let submenu = new Menu({ commands }); submenu.addItem({ command: 'test' }); menu.addItem({ type: 'submenu', submenu }); menu.activeIndex = 0; menu.triggerActiveItem(); expect(submenu.parentMenu).to.equal(null); expect(submenu.activeIndex).to.equal(-1); }); it('should be a no-op if there is no active item', () => { let submenu = new Menu({ commands }); submenu.addItem({ command: 'test' }); menu.addItem({ type: 'submenu', submenu }); menu.open(0, 0); menu.triggerActiveItem(); expect(submenu.parentMenu).to.equal(null); expect(submenu.activeIndex).to.equal(-1); }); }); describe('#addItem()', () => { it('should add a menu item to the end of the menu', () => { menu.addItem({}); let item = menu.addItem({ command: 'test' }); expect(menu.items[1]).to.equal(item); }); }); describe('#insertItem()', () => { it('should insert a menu item into the menu at the specified index', () => { let item1 = menu.insertItem(0, { command: 'test' }); let item2 = menu.insertItem(0, { command: 'test-disabled' }); let item3 = menu.insertItem(0, { command: 'test-toggled' }); expect(menu.items[0]).to.equal(item3); expect(menu.items[1]).to.equal(item2); expect(menu.items[2]).to.equal(item1); }); it('should clamp the index to the bounds of the items', () => { let item1 = menu.insertItem(0, { command: 'test' }); let item2 = menu.insertItem(10, { command: 'test-disabled' }); let item3 = menu.insertItem(-10, { command: 'test-toggled' }); expect(menu.items[0]).to.equal(item3); expect(menu.items[1]).to.equal(item1); expect(menu.items[2]).to.equal(item2); }); it('should close the menu if attached', () => { menu.open(0, 0); expect(menu.isAttached).to.equal(true); menu.insertItem(0, { command: 'test' }); expect(menu.isAttached).to.equal(false); }); }); describe('#removeItem()', () => { it('should remove a menu item from the menu by value', () => { menu.removeItem(menu.addItem({ command: 'test' })); expect(menu.items.length).to.equal(0); }); it('should close the menu if it is attached', () => { let item = menu.addItem({ command: 'test' }); menu.open(0, 0); expect(menu.isAttached).to.equal(true); menu.removeItem(item); expect(menu.isAttached).to.equal(false); }); }); describe('#removeItemAt()', () => { it('should remove a menu item from the menu by index', () => { menu.addItem({ command: 'test' }); menu.removeItemAt(0); expect(menu.items.length).to.equal(0); }); it('should close the menu if it is attached', () => { menu.addItem({ command: 'test' }); menu.open(0, 0); expect(menu.isAttached).to.equal(true); menu.removeItemAt(0); expect(menu.isAttached).to.equal(false); }); }); describe('#clearItems()', () => { it('should remove all items from the menu', () => { menu.addItem({ command: 'test-disabled' }); menu.addItem({ command: 'test' }); menu.activeIndex = 1; menu.clearItems(); expect(menu.items.length).to.equal(0); expect(menu.activeIndex).to.equal(-1); }); it('should close the menu if it is attached', () => { menu.addItem({ command: 'test-disabled' }); menu.addItem({ command: 'test' }); menu.open(0, 0); expect(menu.isAttached).to.equal(true); menu.clearItems(); expect(menu.isAttached).to.equal(false); }); }); describe('#open()', () => { it('should open the menu at the specified location', () => { menu.addItem({ command: 'test' }); menu.open(10, 10); expect(menu.node.style.left).to.equal('10px'); expect(menu.node.style.top).to.equal('10px'); }); it('should be adjusted to fit naturally on the screen', () => { menu.addItem({ command: 'test' }); menu.open(-10, 10000); expect(menu.node.style.left).to.equal('0px'); expect(menu.node.style.top).to.not.equal('10000px'); }); it('should accept flags to force the location', () => { menu.addItem({ command: 'test' }); menu.open(10000, 10000, { forceX: true, forceY: true }); expect(menu.node.style.left).to.equal('10000px'); expect(menu.node.style.top).to.equal('10000px'); }); it('should bail if already attached', () => { menu.addItem({ command: 'test' }); menu.open(10, 10); menu.open(100, 100); expect(menu.node.style.left).to.equal('10px'); expect(menu.node.style.top).to.equal('10px'); }); }); describe('#handleEvent()', () => { context('keydown', () => { it('should trigger the active item on enter', () => { menu.addItem({ command: 'test' }); menu.activeIndex = 0; menu.open(0, 0); simulate(menu.node, 'keydown', { keyCode: 13 }); expect(executed).to.equal('test'); }); it('should close the menu on escape', () => { menu.open(0, 0); expect(menu.isAttached).to.equal(true); simulate(menu.node, 'keydown', { keyCode: 27 }); expect(menu.isAttached).to.equal(false); }); it('should close the menu on left arrow if there is a parent menu', () => { let submenu = new Menu({ commands }); submenu.addItem({ command: 'test' }); menu.addItem({ type: 'submenu', submenu }); menu.open(0, 0); menu.activateNextItem(); menu.triggerActiveItem(); expect(menu.childMenu).to.equal(submenu); simulate(submenu.node, 'keydown', { keyCode: 37 }); expect(menu.childMenu).to.equal(null); }); it('should activate the previous item on up arrow', () => { menu.addItem({ command: 'test' }); menu.addItem({ command: 'test' }); menu.addItem({ command: 'test' }); menu.open(0, 0); simulate(menu.node, 'keydown', { keyCode: 38 }); expect(menu.activeIndex).to.equal(2); }); it('should trigger the active item on right arrow if the item is a submenu', () => { let submenu = new Menu({ commands }); submenu.addItem({ command: 'test' }); menu.addItem({ type: 'submenu', submenu }); menu.open(0, 0); menu.activateNextItem(); expect(menu.childMenu).to.equal(null); simulate(menu.node, 'keydown', { keyCode: 39 }); expect(menu.childMenu).to.equal(submenu); }); it('should activate the next itom on down arrow', () => { menu.addItem({ command: 'test' }); menu.addItem({ command: 'test' }); menu.open(0, 0); simulate(menu.node, 'keydown', { keyCode: 40 }); expect(menu.activeIndex).to.equal(0); }); it('should activate the first matching mnemonic', () => { let submenu1 = new Menu({ commands }); submenu1.title.label = 'foo'; submenu1.title.mnemonic = 0; submenu1.addItem({ command: 'test' }); let submenu2 = new Menu({ commands }); submenu2.title.label = 'bar'; submenu2.title.mnemonic = 0; submenu2.addItem({ command: 'test' }); menu.addItem({ type: 'submenu', submenu: submenu1 }); menu.addItem({ type: 'separator' }); menu.addItem({ type: 'submenu', submenu: submenu2 }); menu.open(0, 0); simulate(menu.node, 'keydown', { keyCode: 70 }); // F expect(menu.activeIndex).to.equal(0); }); it('should activate an item with no matching mnemonic, but matching first character', () => { menu.addItem({ command: 'test' }); menu.addItem({ command: 'test-disabled' }); menu.addItem({ command: 'test-toggled' }); menu.addItem({ command: 'test-hidden' }); menu.addItem({ command: 'test-zenith' }); menu.open(0, 0); expect(menu.activeIndex).to.equal(-1); simulate(menu.node, 'keydown', { keyCode: 90 }); // Z expect(menu.activeIndex).to.equal(4); }); }); context('mouseup', () => { it('should trigger the active item', () => { menu.addItem({ command: 'test' }); menu.activeIndex = 0; menu.open(0, 0); simulate(menu.node, 'mouseup'); expect(executed).to.equal('test'); }); it('should bail if not a left mouse button', () => { menu.addItem({ command: 'test' }); menu.activeIndex = 0; menu.open(0, 0); simulate(menu.node, 'mouseup', { button: 1 }); expect(executed).to.equal(''); }); }); context('mousemove', () => { it('should set the active index', () => { menu.addItem({ command: 'test' }); menu.open(0, 0); let node = menu.node.getElementsByClassName('lm-Menu-item')[0]; let rect = node.getBoundingClientRect(); simulate(menu.node, 'mousemove', { clientX: rect.left, clientY: rect.top }); expect(menu.activeIndex).to.equal(0); }); it('should open a child menu after a timeout', done => { let submenu = new Menu({ commands }); submenu.addItem({ command: 'test' }); submenu.title.label = 'Test Label'; menu.addItem({ type: 'submenu', submenu }); menu.open(0, 0); let node = menu.node.getElementsByClassName('lm-Menu-item')[0]; let rect = node.getBoundingClientRect(); simulate(menu.node, 'mousemove', { clientX: rect.left, clientY: rect.top }); expect(menu.activeIndex).to.equal(0); expect(submenu.isAttached).to.equal(false); setTimeout(() => { expect(submenu.isAttached).to.equal(true); done(); }, 500); }); it('should close an open sub menu', done => { let submenu = new Menu({ commands }); submenu.addItem({ command: 'test' }); submenu.title.label = 'Test Label'; menu.addItem({ command: 'test' }); menu.addItem({ type: 'submenu', submenu }); menu.open(0, 0); menu.activeIndex = 1; menu.triggerActiveItem(); let node = menu.node.getElementsByClassName('lm-Menu-item')[0]; let rect = node.getBoundingClientRect(); simulate(menu.node, 'mousemove', { clientX: rect.left, clientY: rect.top }); expect(menu.activeIndex).to.equal(0); expect(submenu.isAttached).to.equal(true); setTimeout(() => { expect(submenu.isAttached).to.equal(false); done(); }, 500); }); }); context('mouseleave', () => { it('should reset the active index', () => { let submenu = new Menu({ commands }); submenu.addItem({ command: 'test' }); submenu.title.label = 'Test Label'; menu.addItem({ type: 'submenu', submenu }); menu.open(0, 0); let node = menu.node.getElementsByClassName('lm-Menu-item')[0]; let rect = node.getBoundingClientRect(); simulate(menu.node, 'mousemove', { clientX: rect.left, clientY: rect.top }); expect(menu.activeIndex).to.equal(0); simulate(menu.node, 'mouseleave', { clientX: rect.left, clientY: rect.top }); expect(menu.activeIndex).to.equal(-1); menu.dispose(); }); }); context('mousedown', () => { it('should not close the menu if on a child node', () => { menu.addItem({ command: 'test' }); menu.open(0, 0); expect(menu.isAttached).to.equal(true); let rect = menu.node.getBoundingClientRect(); simulate(menu.node, 'mousedown', { clientX: rect.left, clientY: rect.top }); expect(menu.isAttached).to.equal(true); }); it('should close the menu if not on a child node', () => { menu.addItem({ command: 'test' }); menu.open(0, 0); expect(menu.isAttached).to.equal(true); simulate(menu.node, 'mousedown', { clientX: -10 }); expect(menu.isAttached).to.equal(false); }); }); }); describe('#onBeforeAttach()', () => { it('should add event listeners', () => { let node = logMenu.node; logMenu.open(0, 0); expect(logMenu.methods).to.contain('onBeforeAttach'); simulate(node, 'keydown'); expect(logMenu.events).to.contain('keydown'); simulate(node, 'mouseup'); expect(logMenu.events).to.contain('mouseup'); simulate(node, 'mousemove'); expect(logMenu.events).to.contain('mousemove'); simulate(node, 'mouseenter'); expect(logMenu.events).to.contain('mouseenter'); simulate(node, 'mouseleave'); expect(logMenu.events).to.contain('mouseleave'); simulate(node, 'contextmenu'); expect(logMenu.events).to.contain('contextmenu'); simulate(document.body, 'mousedown'); expect(logMenu.events).to.contain('mousedown'); }); }); describe('#onAfterDetach()', () => { it('should remove event listeners', () => { let node = logMenu.node; logMenu.open(0, 0); logMenu.close(); expect(logMenu.methods).to.contain('onAfterDetach'); simulate(node, 'keydown'); expect(logMenu.events).to.not.contain('keydown'); simulate(node, 'mouseup'); expect(logMenu.events).to.not.contain('mouseup'); simulate(node, 'mousemove'); expect(logMenu.events).to.not.contain('mousemove'); simulate(node, 'mouseenter'); expect(logMenu.events).to.not.contain('mouseenter'); simulate(node, 'mouseleave'); expect(logMenu.events).to.not.contain('mouseleave'); simulate(node, 'contextmenu'); expect(logMenu.events).to.not.contain('contextmenu'); simulate(document.body, 'mousedown'); expect(logMenu.events).to.not.contain('mousedown'); }); }); describe('#onActivateRequest', () => { it('should focus the menu', done => { logMenu.open(0, 0); expect(document.activeElement).to.not.equal(logMenu.node); expect(logMenu.methods).to.not.contain('onActivateRequest'); requestAnimationFrame(() => { expect(document.activeElement).to.equal(logMenu.node); expect(logMenu.methods).to.contain('onActivateRequest'); done(); }); }); }); describe('#onUpdateRequest()', () => { it('should be called prior to opening', () => { expect(logMenu.methods).to.not.contain('onUpdateRequest'); logMenu.open(0, 0); expect(logMenu.methods).to.contain('onUpdateRequest'); }); it('should collapse extra separators', () => { menu.addItem({ type: 'separator' }); menu.addItem({ command: 'test' }); menu.addItem({ type: 'separator' }); menu.addItem({ type: 'separator' }); menu.addItem({ type: 'submenu', submenu: new Menu({ commands }) }); menu.addItem({ type: 'separator' }); menu.open(0, 0); let elements = menu.node.querySelectorAll( '.lm-Menu-item[data-type="separator"' ); expect(elements.length).to.equal(4); expect(elements[0].classList.contains('lm-mod-collapsed')).to.equal( true ); expect(elements[1].classList.contains('lm-mod-collapsed')).to.equal( false ); expect(elements[2].classList.contains('lm-mod-collapsed')).to.equal( true ); expect(elements[3].classList.contains('lm-mod-collapsed')).to.equal( true ); }); }); describe('#onCloseRequest()', () => { it('should reset the active index', () => { menu.addItem({ command: 'test' }); menu.activeIndex = 0; menu.open(0, 0); menu.close(); expect(menu.activeIndex).to.equal(-1); }); it('should close any open child menu', () => { let submenu = new Menu({ commands }); submenu.addItem({ command: 'test' }); menu.addItem({ type: 'submenu', submenu }); menu.open(0, 0); menu.activateNextItem(); menu.triggerActiveItem(); expect(menu.childMenu).to.equal(submenu); expect(submenu.isAttached).equal(true); menu.close(); expect(menu.childMenu).to.equal(null); expect(submenu.isAttached).equal(false); }); it('should remove the menu from its parent and activate the parent', done => { let submenu = new Menu({ commands }); submenu.addItem({ command: 'test' }); menu.addItem({ type: 'submenu', submenu }); menu.open(0, 0); menu.activateNextItem(); menu.triggerActiveItem(); expect(menu.childMenu).to.equal(submenu); expect(submenu.parentMenu).to.equal(menu); expect(submenu.isAttached).to.equal(true); submenu.close(); expect(menu.childMenu).to.equal(null); expect(submenu.parentMenu).to.equal(null); expect(submenu.isAttached).to.equal(false); requestAnimationFrame(() => { expect(document.activeElement).to.equal(menu.node); done(); }); }); it('should emit the `aboutToClose` signal if attached', () => { let called = false; menu.open(0, 0); menu.aboutToClose.connect((sender, args) => { expect(sender).to.equal(menu); expect(args).to.equal(undefined); called = true; }); menu.close(); expect(called).to.equal(true); }); }); describe('.IItem', () => { describe('#type', () => { it('should get the type of the menu item', () => { let item = menu.addItem({ type: 'separator' }); expect(item.type).to.equal('separator'); }); it("should default to `'command'`", () => { let item = menu.addItem({}); expect(item.type).to.equal('command'); }); }); describe('#command', () => { it('should get the command to execute when the item is triggered', () => { let item = menu.addItem({ command: 'foo' }); expect(item.command).to.equal('foo'); }); it('should default to an empty string', () => { let item = menu.addItem({}); expect(item.command).to.equal(''); }); }); describe('#args', () => { it('should get the arguments for the command', () => { let item = menu.addItem({ args: { foo: 1 } }); expect(item.args).to.deep.equal({ foo: 1 }); }); it('should default to an empty object', () => { let item = menu.addItem({}); expect(item.args).to.deep.equal({}); }); }); describe('#submenu', () => { it('should get the submenu for the item', () => { let submenu = new Menu({ commands }); let item = menu.addItem({ submenu }); expect(item.submenu).to.equal(submenu); }); it('should default to `null`', () => { let item = menu.addItem({}); expect(item.submenu).to.equal(null); }); }); describe('#label', () => { it('should get the label of a command item for a `command` type', () => { let item = menu.addItem({ command: 'test' }); expect(item.label).to.equal('Test Label'); }); it('should get the title label of a submenu item for a `submenu` type', () => { let submenu = new Menu({ commands }); submenu.title.label = 'foo'; let item = menu.addItem({ type: 'submenu', submenu }); expect(item.label).to.equal('foo'); }); it('should default to an empty string', () => { let item = menu.addItem({}); expect(item.label).to.equal(''); item = menu.addItem({ type: 'separator' }); expect(item.label).to.equal(''); }); }); describe('#mnemonic', () => { it('should get the mnemonic index of a command item for a `command` type', () => { let item = menu.addItem({ command: 'test' }); expect(item.mnemonic).to.equal(0); }); it('should get the title mnemonic of a submenu item for a `submenu` type', () => { let submenu = new Menu({ commands }); submenu.title.mnemonic = 1; let item = menu.addItem({ type: 'submenu', submenu }); expect(item.mnemonic).to.equal(1); }); it('should default to `-1`', () => { let item = menu.addItem({}); expect(item.mnemonic).to.equal(-1); item = menu.addItem({ type: 'separator' }); expect(item.mnemonic).to.equal(-1); }); }); describe('#icon', () => { it('should get the icon class of a command item for a `command` type', () => { let item = menu.addItem({ command: 'test' }); expect(item.icon).to.equal('foo'); }); it('should get the title icon of a submenu item for a `submenu` type', () => { let submenu = new Menu({ commands }); submenu.title.icon = 'bar'; let item = menu.addItem({ type: 'submenu', submenu }); expect(item.icon).to.equal('bar'); }); it('should default to an empty string', () => { let item = menu.addItem({}); expect(item.icon).to.equal(''); item = menu.addItem({ type: 'separator' }); expect(item.icon).to.equal(''); }); }); describe('#caption', () => { it('should get the caption of a command item for a `command` type', () => { let item = menu.addItem({ command: 'test' }); expect(item.caption).to.equal('Test Caption'); }); it('should get the title caption of a submenu item for a `submenu` type', () => { let submenu = new Menu({ commands }); submenu.title.caption = 'foo caption'; let item = menu.addItem({ type: 'submenu', submenu }); expect(item.caption).to.equal('foo caption'); }); it('should default to an empty string', () => { let item = menu.addItem({}); expect(item.caption).to.equal(''); item = menu.addItem({ type: 'separator' }); expect(item.caption).to.equal(''); }); }); describe('#className', () => { it('should get the extra class name of a command item for a `command` type', () => { let item = menu.addItem({ command: 'test' }); expect(item.className).to.equal('testClass'); }); it('should get the title extra class name of a submenu item for a `submenu` type', () => { let submenu = new Menu({ commands }); submenu.title.className = 'fooClass'; let item = menu.addItem({ type: 'submenu', submenu }); expect(item.className).to.equal('fooClass'); }); it('should default to an empty string', () => { let item = menu.addItem({}); expect(item.className).to.equal(''); item = menu.addItem({ type: 'separator' }); expect(item.className).to.equal(''); }); }); describe('#isEnabled', () => { it('should get whether the command is enabled for a `command` type', () => { let item = menu.addItem({ command: 'test-disabled' }); expect(item.isEnabled).to.equal(false); item = menu.addItem({ type: 'command' }); expect(item.isEnabled).to.equal(false); item = menu.addItem({ command: 'test' }); expect(item.isEnabled).to.equal(true); }); it('should get whether there is a submenu for a `submenu` type', () => { let submenu = new Menu({ commands }); let item = menu.addItem({ type: 'submenu', submenu }); expect(item.isEnabled).to.equal(true); item = menu.addItem({ type: 'submenu' }); expect(item.isEnabled).to.equal(false); }); it('should be `true` for a separator item', () => { let item = menu.addItem({ type: 'separator' }); expect(item.isEnabled).to.equal(true); }); }); describe('#isToggled', () => { it('should get whether the command is toggled for a `command` type', () => { let item = menu.addItem({ command: 'test-toggled' }); expect(item.isToggled).to.equal(true); item = menu.addItem({ command: 'test' }); expect(item.isToggled).to.equal(false); item = menu.addItem({ type: 'command' }); expect(item.isToggled).to.equal(false); }); it('should be `false` for other item types', () => { let item = menu.addItem({ type: 'separator' }); expect(item.isToggled).to.equal(false); item = menu.addItem({ type: 'submenu' }); expect(item.isToggled).to.equal(false); }); }); describe('#isVisible', () => { it('should get whether the command is visible for a `command` type', () => { let item = menu.addItem({ command: 'test-hidden' }); expect(item.isVisible).to.equal(false); item = menu.addItem({ command: 'test' }); expect(item.isVisible).to.equal(true); }); it('should get whether there is a submenu for a `submenu` type', () => { let submenu = new Menu({ commands }); let item = menu.addItem({ type: 'submenu', submenu }); expect(item.isVisible).to.equal(true); item = menu.addItem({ type: 'submenu' }); expect(item.isVisible).to.equal(false); }); it('should be `true` for a separator item', () => { let item = menu.addItem({ type: 'separator' }); expect(item.isVisible).to.equal(true); }); }); describe('#keyBinding', () => { it('should get the key binding for the menu item', () => { let item = menu.addItem({ command: 'test' }); expect(item.keyBinding!.keys).to.deep.equal(['Ctrl T']); }); it('should be `null` for submenus and separators', () => { let item = menu.addItem({ type: 'separator' }); expect(item.keyBinding).to.equal(null); item = menu.addItem({ type: 'submenu' }); expect(item.keyBinding).to.equal(null); }); }); }); describe('.Renderer', () => { let renderer = new Menu.Renderer(); describe('#renderItem()', () => { it('should render an item node for the menu', () => { let item = menu.addItem({ command: 'test' }); let vNode = renderer.renderItem({ item, active: false, collapsed: false }); let node = VirtualDOM.realize(vNode); expect(node.classList.contains('lm-Menu-item')).to.equal(true); expect(node.classList.contains('lm-mod-hidden')).to.equal(false); expect(node.classList.contains('lm-mod-disabled')).to.equal(false); expect(node.classList.contains('lm-mod-toggled')).to.equal(false); expect(node.classList.contains('lm-mod-active')).to.equal(false); expect(node.classList.contains('lm-mod-collapsed')).to.equal(false); expect(node.getAttribute('data-command')).to.equal('test'); expect(node.getAttribute('data-type')).to.equal('command'); expect(node.querySelector('.lm-Menu-itemIcon')).to.not.equal(null); expect(node.querySelector('.lm-Menu-itemLabel')).to.not.equal(null); expect(node.querySelector('.lm-Menu-itemSubmenuIcon')).to.not.equal( null ); }); it('should handle the hidden item state', () => { let item = menu.addItem({ command: 'test-hidden' }); let vNode = renderer.renderItem({ item, active: false, collapsed: false }); let node = VirtualDOM.realize(vNode); expect(node.classList.contains('lm-mod-hidden')).to.equal(true); }); it('should handle the disabled item state', () => { let item = menu.addItem({ command: 'test-disabled' }); let vNode = renderer.renderItem({ item, active: false, collapsed: false }); let node = VirtualDOM.realize(vNode); expect(node.classList.contains('lm-mod-disabled')).to.equal(true); }); it('should handle the toggled item state', () => { let item = menu.addItem({ command: 'test-toggled' }); let vNode = renderer.renderItem({ item, active: false, collapsed: false }); let node = VirtualDOM.realize(vNode); expect(node.classList.contains('lm-mod-toggled')).to.equal(true); }); it('should handle the active item state', () => { let item = menu.addItem({ command: 'test' }); let vNode = renderer.renderItem({ item, active: true, collapsed: false }); let node = VirtualDOM.realize(vNode); expect(node.classList.contains('lm-mod-active')).to.equal(true); }); it('should handle the collapsed item state', () => { let item = menu.addItem({ command: 'test-collapsed' }); let vNode = renderer.renderItem({ item, active: false, collapsed: true }); let node = VirtualDOM.realize(vNode); expect(node.classList.contains('lm-mod-collapsed')).to.equal(true); }); }); describe('#renderIcon()', () => { it('should render the icon node for the menu', () => { let item = menu.addItem({ command: 'test' }); let vNode = renderer.renderIcon({ item, active: false, collapsed: false }); let node = VirtualDOM.realize(vNode); expect(node.classList.contains('lm-Menu-itemIcon')).to.equal(true); expect(node.classList.contains('foo')).to.equal(true); }); }); describe('#renderLabel()', () => { it('should render the label node for the menu', () => { let item = menu.addItem({ command: 'test' }); let vNode = renderer.renderLabel({ item, active: false, collapsed: false }); let node = VirtualDOM.realize(vNode); let span = 'Test Label'; /* */ span = 'Test Label'; /* */ expect(node.classList.contains('lm-Menu-itemLabel')).to.equal(true); expect(node.innerHTML).to.equal(span); }); }); describe('#renderShortcut()', () => { it('should render the shortcut node for the menu', () => { let item = menu.addItem({ command: 'test' }); let vNode = renderer.renderShortcut({ item, active: false, collapsed: false }); let node = VirtualDOM.realize(vNode); expect(node.classList.contains('lm-Menu-itemShortcut')).to.equal( true ); if (Platform.IS_MAC) { expect(node.innerHTML).to.equal('\u2303 T'); } else { expect(node.innerHTML).to.equal('Ctrl+T'); } }); }); describe('#renderSubmenu()', () => { it('should render the submenu icon node for the menu', () => { let item = menu.addItem({ command: 'test' }); let vNode = renderer.renderSubmenu({ item, active: false, collapsed: false }); let node = VirtualDOM.realize(vNode); expect(node.classList.contains('lm-Menu-itemSubmenuIcon')).to.equal( true ); }); }); describe('#createItemClass()', () => { it('should create the full class name for the item node', () => { let item = menu.addItem({ command: 'test' }); let name = renderer.createItemClass({ item, active: false, collapsed: false }); let expected = 'lm-Menu-item testClass'; /* */ expected = 'lm-Menu-item p-Menu-item testClass'; /* */ expect(name).to.equal(expected); name = renderer.createItemClass({ item, active: true, collapsed: false }); expected = 'lm-Menu-item lm-mod-active testClass'; /* */ expected = 'lm-Menu-item p-Menu-item lm-mod-active p-mod-active testClass'; /* */ expect(name).to.equal(expected); name = renderer.createItemClass({ item, active: false, collapsed: true }); expected = 'lm-Menu-item lm-mod-collapsed testClass'; /* */ expected = 'lm-Menu-item p-Menu-item lm-mod-collapsed p-mod-collapsed testClass'; /* */ expect(name).to.equal(expected); item = menu.addItem({ command: 'test-disabled' }); name = renderer.createItemClass({ item, active: false, collapsed: false }); expected = 'lm-Menu-item lm-mod-disabled testClass'; /* */ expected = 'lm-Menu-item p-Menu-item lm-mod-disabled p-mod-disabled testClass'; /* */ expect(name).to.equal(expected); item = menu.addItem({ command: 'test-toggled' }); name = renderer.createItemClass({ item, active: false, collapsed: false }); expected = 'lm-Menu-item lm-mod-toggled testClass'; /* */ expected = 'lm-Menu-item p-Menu-item lm-mod-toggled p-mod-toggled testClass'; /* */ expect(name).to.equal(expected); item = menu.addItem({ command: 'test-hidden' }); name = renderer.createItemClass({ item, active: false, collapsed: false }); expected = 'lm-Menu-item lm-mod-hidden testClass'; /* */ expected = 'lm-Menu-item p-Menu-item lm-mod-hidden p-mod-hidden testClass'; /* */ expect(name).to.equal(expected); let submenu = new Menu({ commands }); submenu.title.className = 'fooClass'; item = menu.addItem({ type: 'submenu', submenu }); name = renderer.createItemClass({ item, active: false, collapsed: false }); expected = 'lm-Menu-item fooClass'; /* */ expected = 'lm-Menu-item p-Menu-item fooClass'; /* */ expect(name).to.equal(expected); }); }); describe('#createItemDataset()', () => { it('should create the item dataset', () => { let item = menu.addItem({ command: 'test' }); let dataset = renderer.createItemDataset({ item, active: false, collapsed: false }); expect(dataset).to.deep.equal({ type: 'command', command: 'test' }); item = menu.addItem({ type: 'separator' }); dataset = renderer.createItemDataset({ item, active: false, collapsed: false }); expect(dataset).to.deep.equal({ type: 'separator' }); let submenu = new Menu({ commands }); item = menu.addItem({ type: 'submenu', submenu }); dataset = renderer.createItemDataset({ item, active: false, collapsed: false }); expect(dataset).to.deep.equal({ type: 'submenu' }); }); }); describe('#createIconClass()', () => { it('should create the icon class name', () => { let item = menu.addItem({ command: 'test' }); let name = renderer.createIconClass({ item, active: false, collapsed: false }); let expected = 'lm-Menu-itemIcon foo'; /* */ expected = 'lm-Menu-itemIcon p-Menu-itemIcon foo'; /* */ expect(name).to.equal(expected); item = menu.addItem({ type: 'separator' }); name = renderer.createIconClass({ item, active: false, collapsed: false }); expected = 'lm-Menu-itemIcon'; /* */ expected = 'lm-Menu-itemIcon p-Menu-itemIcon'; /* */ expect(name).to.equal(expected); let submenu = new Menu({ commands }); submenu.title.icon = 'bar'; item = menu.addItem({ type: 'submenu', submenu }); name = renderer.createIconClass({ item, active: false, collapsed: false }); expected = 'lm-Menu-itemIcon bar'; /* */ expected = 'lm-Menu-itemIcon p-Menu-itemIcon bar'; /* */ expect(name).to.equal(expected); }); }); describe('#formatLabel()', () => { it('should format the item label', () => { let item = menu.addItem({ command: 'test' }); let child = renderer.formatLabel({ item, active: false, collapsed: false }); let node = VirtualDOM.realize(h.div(child)); let span = 'Test Label'; /* */ span = 'Test Label'; /* */ expect(node.innerHTML).to.equal(span); item = menu.addItem({ type: 'separator' }); child = renderer.formatLabel({ item, active: false, collapsed: false }); expect(child).to.equal(''); let submenu = new Menu({ commands }); submenu.title.label = 'Submenu Label'; item = menu.addItem({ type: 'submenu', submenu }); child = renderer.formatLabel({ item, active: false, collapsed: false }); expect(child).to.equal('Submenu Label'); }); }); describe('#formatShortcut()', () => { it('should format the item shortcut', () => { let item = menu.addItem({ command: 'test' }); let child = renderer.formatShortcut({ item, active: false, collapsed: false }); if (Platform.IS_MAC) { expect(child).to.equal('\u2303 T'); } else { expect(child).to.equal('Ctrl+T'); } }); }); }); }); }); lumino-2021.12.13/packages/widgets/tests/src/menubar.spec.ts000066400000000000000000000674321415564225700235440ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { expect } from 'chai'; import { generate, simulate } from 'simulate-event'; import { JSONObject } from '@lumino/coreutils'; import { CommandRegistry } from '@lumino/commands'; import { DisposableSet } from '@lumino/disposable'; import { Message, MessageLoop } from '@lumino/messaging'; import { VirtualDOM, VirtualElement } from '@lumino/virtualdom'; import { Menu, MenuBar, Widget } from '@lumino/widgets'; class LogMenuBar extends MenuBar { events: string[] = []; methods: string[] = []; handleEvent(event: Event): void { super.handleEvent(event); this.events.push(event.type); } protected onBeforeAttach(msg: Message): void { super.onBeforeAttach(msg); this.methods.push('onBeforeAttach'); } protected onAfterDetach(msg: Message): void { super.onAfterDetach(msg); this.methods.push('onAfterDetach'); } protected onUpdateRequest(msg: Message): void { super.onUpdateRequest(msg); this.methods.push('onUpdateRequest'); } } describe('@lumino/widgets', () => { const DEFAULT_CMD = 'menubar.spec.ts:defaultCmd'; const disposables = new DisposableSet(); let commands: CommandRegistry; function createMenuBar(): MenuBar { let bar = new MenuBar(); for (let i = 0; i < 3; i++) { let menu = new Menu({ commands }); let item = menu.addItem({ command: DEFAULT_CMD }); menu.addItem(item); menu.title.label = `Menu${i}`; menu.title.mnemonic = 4; bar.addMenu(menu); } bar.activeIndex = 0; Widget.attach(bar, document.body); // Force an update. MessageLoop.sendMessage(bar, Widget.Msg.UpdateRequest); return bar; } before(() => { commands = new CommandRegistry(); let cmd = commands.addCommand(DEFAULT_CMD, { execute: (args: JSONObject) => { return args; }, label: 'LABEL', icon: 'foo', className: 'bar', isToggled: (args: JSONObject) => { return true; }, mnemonic: 1 }); let kbd = commands.addKeyBinding({ keys: ['A'], selector: '*', command: DEFAULT_CMD }); disposables.add(cmd); disposables.add(kbd); }); after(() => { disposables.dispose(); }); describe('MenuBar', () => { describe('#constructor()', () => { it('should take no arguments', () => { let bar = new MenuBar(); expect(bar).to.be.an.instanceof(MenuBar); }); it('should take options for initializing the menu bar', () => { let renderer = new MenuBar.Renderer(); let bar = new MenuBar({ renderer }); expect(bar).to.be.an.instanceof(MenuBar); }); it('should add the `lm-MenuBar` class', () => { let bar = new MenuBar(); expect(bar.hasClass('lm-MenuBar')).to.equal(true); }); }); describe('#dispose()', () => { it('should dispose of the resources held by the menu bar', () => { let bar = new MenuBar(); bar.addMenu(new Menu({ commands })); bar.dispose(); expect(bar.isDisposed).to.equal(true); bar.dispose(); expect(bar.isDisposed).to.equal(true); }); }); describe('#renderer', () => { it('should get the renderer for the menu bar', () => { let renderer = Object.create(MenuBar.defaultRenderer); let bar = new MenuBar({ renderer }); expect(bar.renderer).to.equal(renderer); }); }); describe('#childMenu', () => { it('should get the child menu of the menu bar', () => { let bar = new MenuBar(); let menu = new Menu({ commands }); bar.addMenu(menu); bar.activeIndex = 0; bar.openActiveMenu(); expect(bar.childMenu).to.equal(menu); bar.dispose(); }); it('should be `null` if there is no open menu', () => { let bar = new MenuBar(); let menu = new Menu({ commands }); bar.addMenu(menu); bar.activeIndex = 0; expect(bar.childMenu).to.equal(null); bar.dispose(); }); }); describe('#contentNode', () => { it('should get the menu content node', () => { let bar = new MenuBar(); let content = bar.contentNode; expect(content.classList.contains('lm-MenuBar-content')).to.equal(true); }); }); describe('#activeMenu', () => { it('should get the active menu of the menu bar', () => { let bar = new MenuBar(); let menu = new Menu({ commands }); bar.addMenu(menu); bar.activeIndex = 0; expect(bar.activeMenu).to.equal(menu); bar.dispose(); }); it('should be `null` if there is no active menu', () => { let bar = new MenuBar(); let menu = new Menu({ commands }); bar.addMenu(menu); expect(bar.activeMenu).to.equal(null); bar.dispose(); }); it('should set the currently active menu', () => { let bar = new MenuBar(); let menu = new Menu({ commands }); bar.addMenu(menu); bar.activeMenu = menu; expect(bar.activeMenu).to.equal(menu); bar.dispose(); }); it('should set to `null` if the menu is not in the menu bar', () => { let bar = new MenuBar(); let menu = new Menu({ commands }); bar.activeMenu = menu; expect(bar.activeMenu).to.equal(null); bar.dispose(); }); }); describe('#activeIndex', () => { it('should get the index of the currently active menu', () => { let bar = new MenuBar(); let menu = new Menu({ commands }); bar.addMenu(menu); bar.activeMenu = menu; expect(bar.activeIndex).to.equal(0); bar.dispose(); }); it('should be `-1` if no menu is active', () => { let bar = new MenuBar(); let menu = new Menu({ commands }); bar.addMenu(menu); expect(bar.activeIndex).to.equal(-1); bar.dispose(); }); it('should set the index of the currently active menu', () => { let bar = new MenuBar(); let menu = new Menu({ commands }); bar.addMenu(menu); bar.activeIndex = 0; expect(bar.activeIndex).to.equal(0); bar.dispose(); }); it('should set to `-1` if the index is out of range', () => { let bar = new MenuBar(); let menu = new Menu({ commands }); bar.addMenu(menu); bar.activeIndex = -2; expect(bar.activeIndex).to.equal(-1); bar.activeIndex = 1; expect(bar.activeIndex).to.equal(-1); bar.dispose(); }); it('should add `lm-mod-active` to the active node', () => { let bar = createMenuBar(); let node = bar.contentNode.firstChild as HTMLElement; expect(node.classList.contains('lm-mod-active')).to.equal(true); expect(bar.activeIndex).to.equal(0); bar.dispose(); }); }); describe('#menus', () => { it('should get a read-only array of the menus in the menu bar', () => { let bar = new MenuBar(); let menu0 = new Menu({ commands }); let menu1 = new Menu({ commands }); bar.addMenu(menu0); bar.addMenu(menu1); let menus = bar.menus; expect(menus.length).to.equal(2); expect(menus[0]).to.equal(menu0); expect(menus[1]).to.equal(menu1); }); }); describe('#openActiveMenu()', () => { it('should open the active menu and activate its first menu item', () => { let bar = new MenuBar(); let menu = new Menu({ commands }); let item = menu.addItem({ command: DEFAULT_CMD }); menu.addItem(item); bar.addMenu(menu); bar.activeMenu = menu; bar.openActiveMenu(); expect(menu.isAttached).to.equal(true); expect(menu.activeItem!.command).to.equal(item.command); bar.dispose(); }); it('should be a no-op if there is no active menu', () => { let bar = new MenuBar(); let menu = new Menu({ commands }); let item = menu.addItem({ command: DEFAULT_CMD }); menu.addItem(item); bar.addMenu(menu); bar.openActiveMenu(); expect(menu.isAttached).to.equal(false); bar.dispose(); }); }); describe('#addMenu()', () => { it('should add a menu to the end of the menu bar', () => { let bar = new MenuBar(); let menu = new Menu({ commands }); let item = menu.addItem({ command: DEFAULT_CMD }); menu.addItem(item); bar.addMenu(new Menu({ commands })); bar.addMenu(menu); expect(bar.menus.length).to.equal(2); expect(bar.menus[1]).to.equal(menu); bar.dispose(); }); it('should move an existing menu to the end', () => { let bar = new MenuBar(); let menu = new Menu({ commands }); let item = menu.addItem({ command: DEFAULT_CMD }); menu.addItem(item); bar.addMenu(menu); bar.addMenu(new Menu({ commands })); bar.addMenu(menu); expect(bar.menus.length).to.equal(2); expect(bar.menus[1]).to.equal(menu); bar.dispose(); }); }); describe('#insertMenu()', () => { it('should insert a menu into the menu bar at the specified index', () => { let bar = new MenuBar(); let menu = new Menu({ commands }); bar.addMenu(new Menu({ commands })); bar.insertMenu(0, menu); expect(bar.menus.length).to.equal(2); expect(bar.menus[0]).to.equal(menu); bar.dispose(); }); it('should clamp the index to the bounds of the menus', () => { let bar = new MenuBar(); let menu = new Menu({ commands }); bar.addMenu(new Menu({ commands })); bar.insertMenu(-1, menu); expect(bar.menus.length).to.equal(2); expect(bar.menus[0]).to.equal(menu); menu = new Menu({ commands }); bar.insertMenu(10, menu); expect(bar.menus.length).to.equal(3); expect(bar.menus[2]).to.equal(menu); bar.dispose(); }); it('should move an existing menu', () => { let bar = new MenuBar(); let menu = new Menu({ commands }); bar.addMenu(new Menu({ commands })); bar.insertMenu(0, menu); bar.insertMenu(10, menu); expect(bar.menus.length).to.equal(2); expect(bar.menus[1]).to.equal(menu); bar.dispose(); }); it('should be a no-op if there is no effective move', () => { let bar = new MenuBar(); let menu = new Menu({ commands }); bar.addMenu(new Menu({ commands })); bar.insertMenu(0, menu); bar.insertMenu(0, menu); expect(bar.menus.length).to.equal(2); expect(bar.menus[0]).to.equal(menu); bar.dispose(); }); }); describe('#removeMenu()', () => { it('should remove a menu from the menu bar by value', () => { let bar = new MenuBar(); let menu = new Menu({ commands }); bar.addMenu(new Menu({ commands })); bar.addMenu(menu); bar.removeMenu(menu); expect(bar.menus.length).to.equal(1); expect(bar.menus[0]).to.not.equal(menu); bar.dispose(); }); it('should return be a no-op if the menu is not in the menu bar', () => { let bar = new MenuBar(); let menu = new Menu({ commands }); bar.addMenu(new Menu({ commands })); bar.addMenu(menu); bar.removeMenu(menu); bar.removeMenu(menu); expect(bar.menus.length).to.equal(0); bar.dispose(); }); }); describe('#removeMenuAt()', () => { it('should remove a menu from the menu bar by index', () => { let bar = new MenuBar(); let menu = new Menu({ commands }); bar.addMenu(new Menu({ commands })); bar.addMenu(menu); bar.removeMenuAt(1); expect(bar.menus.length).to.equal(1); expect(bar.menus[0]).to.not.equal(menu); bar.dispose(); }); it('should be a no-op if the index is out of range', () => { let bar = new MenuBar(); let menu = new Menu({ commands }); bar.addMenu(new Menu({ commands })); bar.addMenu(menu); bar.removeMenuAt(1); bar.removeMenuAt(1); expect(bar.menus.length).to.equal(1); bar.dispose(); }); }); describe('#clearMenus()', () => { it('should remove all menus from the menu bar', () => { let bar = new MenuBar(); bar.addMenu(new Menu({ commands })); bar.addMenu(new Menu({ commands })); bar.clearMenus(); expect(bar.menus).to.eql([]); }); it('should be a no-op if there are no menus', () => { let bar = new MenuBar(); bar.clearMenus(); expect(bar.menus).to.eql([]); }); }); describe('#handleEvent()', () => { let bar: MenuBar; beforeEach(() => { bar = createMenuBar(); }); afterEach(() => { bar.dispose(); }); context('keydown', () => { it('should open the active menu on Enter', () => { let menu = bar.activeMenu!; simulate(bar.node, 'keydown', { keyCode: 13 }); expect(menu.isAttached).to.equal(true); }); it('should open the active menu on Up Arrow', () => { let menu = bar.activeMenu!; simulate(bar.node, 'keydown', { keyCode: 38 }); expect(menu.isAttached).to.equal(true); }); it('should open the active menu on Down Arrow', () => { let menu = bar.activeMenu!; simulate(bar.node, 'keydown', { keyCode: 40 }); expect(menu.isAttached).to.equal(true); }); it('should close the active menu on Escape', () => { let menu = bar.activeMenu!; bar.openActiveMenu(); simulate(bar.node, 'keydown', { keyCode: 27 }); expect(menu.isAttached).to.equal(false); expect(menu.activeIndex).to.equal(-1); expect(menu.node.contains(document.activeElement)).to.equal(false); }); it('should activate the previous menu on Left Arrow', () => { simulate(bar.node, 'keydown', { keyCode: 37 }); expect(bar.activeIndex!).to.equal(2); simulate(bar.node, 'keydown', { keyCode: 37 }); expect(bar.activeIndex!).to.equal(1); }); it('should activate the next menu on Right Arrow', () => { simulate(bar.node, 'keydown', { keyCode: 39 }); expect(bar.activeIndex!).to.equal(1); simulate(bar.node, 'keydown', { keyCode: 39 }); expect(bar.activeIndex!).to.equal(2); simulate(bar.node, 'keydown', { keyCode: 39 }); expect(bar.activeIndex!).to.equal(0); }); it('should open the menu matching a mnemonic', () => { simulate(bar.node, 'keydown', { keyCode: 97 }); // '1'; expect(bar.activeIndex!).to.equal(1); let menu = bar.activeMenu!; expect(menu.isAttached).to.equal(true); }); it('should select the next menu matching by first letter', () => { bar.activeIndex = 1; simulate(bar.node, 'keydown', { keyCode: 77 }); // 'M'; expect(bar.activeIndex!).to.equal(1); let menu = bar.activeMenu!; expect(menu.isAttached).to.equal(false); }); it('should select the first menu matching the mnemonic', () => { let menu = new Menu({ commands }); menu.title.label = 'Test1'; menu.title.mnemonic = 4; bar.addMenu(menu); simulate(bar.node, 'keydown', { keyCode: 97 }); // '1'; expect(bar.activeIndex).to.equal(1); menu = bar.activeMenu!; expect(menu.isAttached).to.equal(false); }); it('should select the only menu matching the first letter', () => { let menu = new Menu({ commands }); menu.title.label = 'Test1'; bar.addMenu(menu); bar.addMenu(new Menu({ commands })); simulate(bar.node, 'keydown', { keyCode: 84 }); // 'T'; expect(bar.activeIndex).to.equal(3); menu = bar.activeMenu!; expect(menu.isAttached).to.equal(false); }); }); context('mousedown', () => { it('should bail if the mouse press was not on the menu bar', () => { let evt = generate('mousedown', { clientX: -10 }); bar.node.dispatchEvent(evt); expect(evt.defaultPrevented).to.equal(false); }); it('should close an open menu if the press was not on an item', () => { bar.openActiveMenu(); let menu = bar.activeMenu!; simulate(bar.node, 'mousedown'); expect(bar.activeIndex).to.equal(-1); expect(menu.isAttached).to.equal(false); }); it('should close an active menu', () => { bar.openActiveMenu(); let menu = bar.activeMenu!; let node = bar.node.getElementsByClassName( 'lm-MenuBar-item' )[0] as HTMLElement; let rect = node.getBoundingClientRect(); simulate(bar.node, 'mousedown', { clientX: rect.left, clientY: rect.top }); expect(bar.activeIndex).to.equal(0); expect(menu.isAttached).to.equal(false); }); it('should open an active menu', () => { let menu = bar.activeMenu!; let node = bar.node.getElementsByClassName( 'lm-MenuBar-item' )[0] as HTMLElement; let rect = node.getBoundingClientRect(); simulate(bar.node, 'mousedown', { clientX: rect.left, clientY: rect.top }); expect(bar.activeIndex).to.equal(0); expect(menu.isAttached).to.equal(true); }); it('should not close an active menu if not a left mouse press', () => { bar.openActiveMenu(); let menu = bar.activeMenu!; let node = bar.node.getElementsByClassName( 'lm-MenuBar-item' )[0] as HTMLElement; let rect = node.getBoundingClientRect(); simulate(bar.node, 'mousedown', { button: 1, clientX: rect.left, clientY: rect.top }); expect(bar.activeIndex).to.equal(0); expect(menu.isAttached).to.equal(true); }); }); context('mousemove', () => { it('should open a new menu if a menu is already open', () => { bar.openActiveMenu(); let menu = bar.activeMenu!; let node = bar.node.getElementsByClassName( 'lm-MenuBar-item' )[1] as HTMLElement; let rect = node.getBoundingClientRect(); simulate(node, 'mousemove', { clientX: rect.left + 1, clientY: rect.top }); expect(bar.activeIndex).to.equal(1); expect(menu.isAttached).to.equal(false); expect(bar.activeMenu!.isAttached).to.equal(true); }); it('should be a no-op if the active index will not change', () => { bar.openActiveMenu(); let menu = bar.activeMenu!; let node = bar.node.getElementsByClassName( 'lm-MenuBar-item' )[0] as HTMLElement; let rect = node.getBoundingClientRect(); simulate(bar.node, 'mousemove', { clientX: rect.left, clientY: rect.top + 1 }); expect(bar.activeIndex).to.equal(0); expect(menu.isAttached).to.equal(true); }); it('should be a no-op if the mouse is not over an item and there is a menu open', () => { bar.openActiveMenu(); let menu = bar.activeMenu!; simulate(bar.node, 'mousemove'); expect(bar.activeIndex).to.equal(0); expect(menu.isAttached).to.equal(true); }); }); context('mouseleave', () => { it('should reset the active index if there is no open menu', () => { simulate(bar.node, 'mouseleave'); expect(bar.activeIndex).to.equal(-1); }); it('should be a no-op if there is an open menu', () => { bar.openActiveMenu(); let menu = bar.activeMenu!; simulate(bar.node, 'mouseleave'); expect(bar.activeIndex).to.equal(0); expect(menu.isAttached).to.equal(true); }); }); context('contextmenu', () => { it('should prevent default', () => { let evt = generate('contextmenu'); let cancelled = !bar.node.dispatchEvent(evt); expect(cancelled).to.equal(true); }); }); }); describe('#onBeforeAttach()', () => { it('should add event listeners', () => { let bar = new LogMenuBar(); let node = bar.node; Widget.attach(bar, document.body); expect(bar.methods.indexOf('onBeforeAttach')).to.not.equal(-1); simulate(node, 'keydown'); expect(bar.events.indexOf('keydown')).to.not.equal(-1); simulate(node, 'mousedown'); expect(bar.events.indexOf('mousedown')).to.not.equal(-1); simulate(node, 'mousemove'); expect(bar.events.indexOf('mousemove')).to.not.equal(-1); simulate(node, 'mouseleave'); expect(bar.events.indexOf('mouseleave')).to.not.equal(-1); simulate(node, 'contextmenu'); expect(bar.events.indexOf('contextmenu')).to.not.equal(-1); bar.dispose(); }); }); describe('#onAfterDetach()', () => { it('should remove event listeners', () => { let bar = new LogMenuBar(); let node = bar.node; Widget.attach(bar, document.body); Widget.detach(bar); expect(bar.methods.indexOf('onBeforeAttach')).to.not.equal(-1); simulate(node, 'keydown'); expect(bar.events.indexOf('keydown')).to.equal(-1); simulate(node, 'mousedown'); expect(bar.events.indexOf('mousedown')).to.equal(-1); simulate(node, 'mousemove'); expect(bar.events.indexOf('mousemove')).to.equal(-1); simulate(node, 'mouseleave'); expect(bar.events.indexOf('mouseleave')).to.equal(-1); simulate(node, 'contextmenu'); expect(bar.events.indexOf('contextmenu')).to.equal(-1); bar.dispose(); }); }); describe('#onActivateRequest()', () => { it('should be a no-op if not attached', () => { let bar = createMenuBar(); Widget.detach(bar); MessageLoop.sendMessage(bar, Widget.Msg.ActivateRequest); expect(bar.node.contains(document.activeElement)).to.equal(false); }); it('should focus the node if attached', () => { let bar = createMenuBar(); MessageLoop.sendMessage(bar, Widget.Msg.ActivateRequest); expect(bar.node.contains(document.activeElement)).to.equal(true); }); }); describe('#onUpdateRequest()', () => { it('should be called when the title of a menu changes', done => { let bar = new LogMenuBar(); let menu = new Menu({ commands }); bar.addMenu(menu); bar.activeIndex = 0; expect(bar.methods.indexOf('onUpdateRequest')).to.equal(-1); menu.title.label = 'foo'; expect(bar.methods.indexOf('onUpdateRequest')).to.equal(-1); requestAnimationFrame(() => { expect(bar.methods.indexOf('onUpdateRequest')).to.not.equal(-1); done(); }); }); it('should render the content', () => { let bar = new LogMenuBar(); let menu = new Menu({ commands }); bar.addMenu(menu); expect(bar.contentNode.children.length).to.equal(0); MessageLoop.sendMessage(bar, Widget.Msg.UpdateRequest); let child = bar.contentNode.firstChild as HTMLElement; expect(child.className).to.contain('lm-MenuBar-item'); }); }); context('`menuRequested` signal', () => { it('should activate the next menu', () => { let bar = createMenuBar(); bar.openActiveMenu(); (bar.activeMenu!.menuRequested as any).emit('next'); expect(bar.activeIndex).to.equal(1); bar.dispose(); }); it('should activate the previous menu', () => { let bar = createMenuBar(); bar.openActiveMenu(); (bar.activeMenu!.menuRequested as any).emit('previous'); expect(bar.activeIndex).to.equal(2); bar.dispose(); }); it('should be a no-op if the sender is not the open menu', () => { let bar = createMenuBar(); (bar.activeMenu!.menuRequested as any).emit('next'); expect(bar.activeIndex).to.equal(0); bar.dispose(); }); }); describe('.Renderer', () => { const renderer = new MenuBar.Renderer(); let data: MenuBar.IRenderData; before(() => { let widget = new Widget(); widget.title.label = 'foo'; widget.title.icon = 'bar'; widget.title.className = 'baz'; widget.title.closable = true; data = { title: widget.title, active: true }; }); describe('#renderItem()', () => { it('should render the virtual element for a menu bar item', () => { let node = VirtualDOM.realize(renderer.renderItem(data)); expect(node.classList.contains('lm-MenuBar-item')).to.equal(true); expect( node.getElementsByClassName('lm-MenuBar-itemIcon').length ).to.equal(1); expect( node.getElementsByClassName('lm-MenuBar-itemLabel').length ).to.equal(1); }); }); describe('#renderIcon()', () => { it('should render the icon element for a menu bar item', () => { let node = VirtualDOM.realize(renderer.renderIcon(data)); expect(node.className).to.contain('lm-MenuBar-itemIcon'); expect(node.className).to.contain('bar'); }); }); describe('#renderLabel()', () => { it('should render the label element for a menu item', () => { let node = VirtualDOM.realize(renderer.renderLabel(data)); expect(node.className).to.contain('lm-MenuBar-itemLabel'); expect(node.textContent).to.equal('foo'); }); }); describe('#createItemClass()', () => { it('should create the class name for the menu bar item', () => { let itemClass = renderer.createItemClass(data); expect(itemClass).to.contain('baz'); expect(itemClass).to.contain('lm-mod-active'); }); }); describe('#createIconClass()', () => { it('should create the class name for the menu bar item icon', () => { let iconClass = renderer.createIconClass(data); expect(iconClass).to.contain('lm-MenuBar-itemIcon'); expect(iconClass).to.contain('bar'); }); }); describe('#formatLabel()', () => { it('should format a label into HTML for display', () => { data.title.mnemonic = 1; let label = renderer.formatLabel(data); expect((label as any)[0]).to.equal('f'); let node = VirtualDOM.realize((label as any)[1] as VirtualElement); expect(node.className).to.contain('lm-MenuBar-itemMnemonic'); expect(node.textContent).to.equal('o'); expect((label as any)[2]).to.equal('o'); }); it('should not add a mnemonic if the index is out of range', () => { data.title.mnemonic = -1; let label = renderer.formatLabel(data); expect(label).to.equal('foo'); }); }); }); describe('.defaultRenderer', () => { it('should be an instance of `Renderer`', () => { expect(MenuBar.defaultRenderer).to.be.an.instanceof(MenuBar.Renderer); }); }); }); }); lumino-2021.12.13/packages/widgets/tests/src/panel.spec.ts000066400000000000000000000051551415564225700232040ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { expect } from 'chai'; import { Panel, PanelLayout, Widget } from '@lumino/widgets'; describe('@lumino/widgets', () => { describe('Panel', () => { describe('#constructor()', () => { it('should take no arguments', () => { let panel = new Panel(); expect(panel).to.be.an.instanceof(Panel); }); it('should accept options', () => { let layout = new PanelLayout(); let panel = new Panel({ layout }); expect(panel.layout).to.equal(layout); }); it('should add the `lm-Panel` class', () => { let panel = new Panel(); expect(panel.hasClass('lm-Panel')).to.equal(true); }); }); describe('#widgets', () => { it('should be a read-only array of widgets in the panel', () => { let panel = new Panel(); let widget = new Widget(); panel.addWidget(widget); expect(panel.widgets).to.deep.equal([widget]); }); }); describe('#addWidget()', () => { it('should add a widget to the end of the panel', () => { let panel = new Panel(); let widget = new Widget(); panel.addWidget(new Widget()); panel.addWidget(widget); expect(panel.widgets[1]).to.equal(widget); }); it('should move an existing widget to the end', () => { let panel = new Panel(); let widget = new Widget(); panel.addWidget(widget); panel.addWidget(new Widget()); panel.addWidget(widget); expect(panel.widgets[1]).to.equal(widget); }); }); describe('#insertWidget()', () => { it('should insert a widget at the specified index', () => { let panel = new Panel(); let widget = new Widget(); panel.addWidget(new Widget()); panel.insertWidget(0, widget); expect(panel.widgets[0]).to.equal(widget); }); it('should move an existing widget to the specified index', () => { let panel = new Panel(); let widget = new Widget(); panel.addWidget(new Widget()); panel.addWidget(widget); panel.insertWidget(0, widget); expect(panel.widgets[0]).to.equal(widget); }); }); }); }); lumino-2021.12.13/packages/widgets/tests/src/panellayout.spec.ts000066400000000000000000000253431415564225700244430ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { expect } from 'chai'; import { each, every } from '@lumino/algorithm'; import { IMessageHandler, IMessageHook, Message, MessageLoop } from '@lumino/messaging'; import { PanelLayout, Widget } from '@lumino/widgets'; class LogHook implements IMessageHook { messages: string[] = []; messageHook(target: IMessageHandler, msg: Message): boolean { this.messages.push(msg.type); return true; } } class LogPanelLayout extends PanelLayout { methods: string[] = []; protected init(): void { super.init(); this.methods.push('init'); } protected attachWidget(index: number, widget: Widget): void { super.attachWidget(index, widget); this.methods.push('attachWidget'); } protected moveWidget( fromIndex: number, toIndex: number, widget: Widget ): void { super.moveWidget(fromIndex, toIndex, widget); this.methods.push('moveWidget'); } protected detachWidget(index: number, widget: Widget): void { super.detachWidget(index, widget); this.methods.push('detachWidget'); } protected onChildRemoved(msg: Widget.ChildMessage): void { super.onChildRemoved(msg); this.methods.push('onChildRemoved'); } } describe('@lumino/widgets', () => { describe('PanelLayout', () => { describe('#dispose()', () => { it('should dispose of the resources held by the widget', () => { let layout = new PanelLayout(); let widgets = [new Widget(), new Widget()]; each(widgets, w => { layout.addWidget(w); }); layout.dispose(); expect(every(widgets, w => w.isDisposed)).to.equal(true); }); }); describe('#widgets', () => { it('should be a read-only sequence of widgets in the layout', () => { let layout = new PanelLayout(); let widget = new Widget(); layout.addWidget(widget); let widgets = layout.widgets; expect(widgets.length).to.equal(1); expect(widgets[0]).to.equal(widget); }); }); describe('#iter()', () => { it('should create an iterator over the widgets in the layout', () => { let layout = new PanelLayout(); let widgets = [new Widget(), new Widget()]; each(widgets, w => { layout.addWidget(w); }); each(widgets, w => { w.title.label = 'foo'; }); let iter = layout.iter(); expect(every(iter, w => w.title.label === 'foo')).to.equal(true); expect(layout.iter()).to.not.equal(iter); }); }); describe('#addWidget()', () => { it('should add a widget to the end of the layout', () => { let layout = new PanelLayout(); layout.addWidget(new Widget()); let widget = new Widget(); layout.addWidget(widget); expect(layout.widgets[1]).to.equal(widget); }); it('should move an existing widget to the end', () => { let layout = new PanelLayout(); let widget = new Widget(); layout.addWidget(widget); layout.addWidget(new Widget()); layout.addWidget(widget); expect(layout.widgets[1]).to.equal(widget); }); }); describe('#insertWidget()', () => { it('should insert a widget at the specified index', () => { let layout = new PanelLayout(); layout.addWidget(new Widget()); let widget = new Widget(); layout.insertWidget(0, widget); expect(layout.widgets[0]).to.equal(widget); }); it('should move an existing widget to the specified index', () => { let layout = new PanelLayout(); layout.addWidget(new Widget()); let widget = new Widget(); layout.addWidget(widget); layout.insertWidget(0, widget); expect(layout.widgets[0]).to.equal(widget); }); it('should clamp the index to the bounds of the widgets', () => { let layout = new PanelLayout(); layout.addWidget(new Widget()); let widget = new Widget(); layout.insertWidget(-2, widget); expect(layout.widgets[0]).to.equal(widget); layout.insertWidget(10, widget); expect(layout.widgets[1]).to.equal(widget); }); it('should be a no-op if the index does not change', () => { let layout = new PanelLayout(); let widget = new Widget(); layout.addWidget(widget); layout.addWidget(new Widget()); layout.insertWidget(0, widget); expect(layout.widgets[0]).to.equal(widget); }); }); describe('#removeWidget()', () => { it('should remove a widget by value', () => { let layout = new PanelLayout(); let widget = new Widget(); layout.addWidget(widget); layout.addWidget(new Widget()); layout.removeWidget(widget); expect(layout.widgets.length).to.equal(1); expect(layout.widgets[0]).to.not.equal(widget); }); }); describe('#removeWidgetAt()', () => { it('should remove a widget at a given index', () => { let layout = new PanelLayout(); let widget = new Widget(); layout.addWidget(widget); layout.addWidget(new Widget()); layout.removeWidgetAt(0); expect(layout.widgets.length).to.equal(1); expect(layout.widgets[0]).to.not.equal(widget); }); }); describe('#init()', () => { it('should be invoked when the layout is installed on its parent', () => { let widget = new Widget(); let layout = new LogPanelLayout(); widget.layout = layout; expect(layout.methods).to.contain('init'); }); it('should attach all widgets to the DOM', () => { let parent = new Widget(); Widget.attach(parent, document.body); let layout = new LogPanelLayout(); let widgets = [new Widget(), new Widget(), new Widget()]; each(widgets, w => { layout.addWidget(w); }); parent.layout = layout; expect(every(widgets, w => w.parent === parent)).to.equal(true); expect(every(widgets, w => w.isAttached)).to.equal(true); parent.dispose(); }); }); describe('#attachWidget()', () => { it("should attach a widget to the parent's DOM node", () => { let panel = new Widget(); let layout = new LogPanelLayout(); let widget = new Widget(); panel.layout = layout; layout.insertWidget(0, widget); expect(layout.methods).to.contain('attachWidget'); expect(panel.node.children[0]).to.equal(widget.node); panel.dispose(); }); it('should send before/after attach messages if the parent is attached', () => { let panel = new Widget(); let layout = new LogPanelLayout(); let widget = new Widget(); let hook = new LogHook(); panel.layout = layout; MessageLoop.installMessageHook(widget, hook); Widget.attach(panel, document.body); layout.insertWidget(0, widget); expect(layout.methods).to.contain('attachWidget'); expect(hook.messages).to.contain('before-attach'); expect(hook.messages).to.contain('after-attach'); panel.dispose(); }); }); describe('#moveWidget()', () => { it("should move a widget in the parent's DOM node", () => { let panel = new Widget(); let layout = new LogPanelLayout(); let widget = new Widget(); panel.layout = layout; layout.addWidget(widget); layout.addWidget(new Widget()); layout.insertWidget(1, widget); expect(layout.methods).to.contain('moveWidget'); expect(panel.node.children[1]).to.equal(widget.node); panel.dispose(); }); it('should send before/after detach/attach messages if the parent is attached', () => { let panel = new Widget(); let layout = new LogPanelLayout(); let widget = new Widget(); let hook = new LogHook(); MessageLoop.installMessageHook(widget, hook); panel.layout = layout; Widget.attach(panel, document.body); layout.addWidget(widget); layout.addWidget(new Widget()); layout.insertWidget(1, widget); expect(layout.methods).to.contain('moveWidget'); expect(hook.messages).to.contain('before-detach'); expect(hook.messages).to.contain('after-detach'); expect(hook.messages).to.contain('before-attach'); expect(hook.messages).to.contain('after-attach'); panel.dispose(); }); }); describe('#detachWidget()', () => { it("should detach a widget from the parent's DOM node", () => { let panel = new Widget(); let layout = new LogPanelLayout(); let widget = new Widget(); panel.layout = layout; layout.insertWidget(0, widget); expect(panel.node.children[0]).to.equal(widget.node); layout.removeWidget(widget); expect(layout.methods).to.contain('detachWidget'); panel.dispose(); }); it('should send before/after detach message if the parent is attached', () => { let panel = new Widget(); let layout = new LogPanelLayout(); let widget = new Widget(); let hook = new LogHook(); MessageLoop.installMessageHook(widget, hook); panel.layout = layout; Widget.attach(panel, document.body); layout.insertWidget(0, widget); expect(panel.node.children[0]).to.equal(widget.node); layout.removeWidget(widget); expect(layout.methods).to.contain('detachWidget'); expect(hook.messages).to.contain('before-detach'); expect(hook.messages).to.contain('after-detach'); panel.dispose(); }); }); describe('#onChildRemoved()', () => { it('should be called when a widget is removed from its parent', () => { let panel = new Widget(); let layout = new LogPanelLayout(); let widget = new Widget(); panel.layout = layout; layout.addWidget(widget); widget.parent = null; expect(layout.methods).to.contain('onChildRemoved'); }); it('should remove the widget from the layout', () => { let panel = new Widget(); let layout = new LogPanelLayout(); let widget = new Widget(); panel.layout = layout; layout.addWidget(widget); widget.parent = null; expect(layout.widgets.length).to.equal(0); }); }); }); }); lumino-2021.12.13/packages/widgets/tests/src/splitlayout.spec.ts000066400000000000000000000432001415564225700244670ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { expect } from 'chai'; import { each, every } from '@lumino/algorithm'; import { IMessageHandler, IMessageHook, Message, MessageLoop } from '@lumino/messaging'; import { SplitLayout, Widget } from '@lumino/widgets'; const renderer: SplitLayout.IRenderer = { createHandle: () => document.createElement('div') }; class LogSplitLayout extends SplitLayout { methods: string[] = []; protected init(): void { super.init(); this.methods.push('init'); } protected attachWidget(index: number, widget: Widget): void { super.attachWidget(index, widget); this.methods.push('attachWidget'); } protected moveWidget( fromIndex: number, toIndex: number, widget: Widget ): void { super.moveWidget(fromIndex, toIndex, widget); this.methods.push('moveWidget'); } protected detachWidget(index: number, widget: Widget): void { super.detachWidget(index, widget); this.methods.push('detachWidget'); } protected onAfterShow(msg: Message): void { super.onAfterShow(msg); this.methods.push('onAfterShow'); } protected onAfterAttach(msg: Message): void { super.onAfterAttach(msg); this.methods.push('onAfterAttach'); } protected onChildShown(msg: Widget.ChildMessage): void { super.onChildShown(msg); this.methods.push('onChildShown'); } protected onChildHidden(msg: Widget.ChildMessage): void { super.onChildHidden(msg); this.methods.push('onChildHidden'); } protected onResize(msg: Widget.ResizeMessage): void { super.onResize(msg); this.methods.push('onResize'); } protected onUpdateRequest(msg: Message): void { super.onUpdateRequest(msg); this.methods.push('onUpdateRequest'); } protected onFitRequest(msg: Message): void { super.onFitRequest(msg); this.methods.push('onFitRequest'); } } class LogHook implements IMessageHook { messages: string[] = []; messageHook(target: IMessageHandler, msg: Message): boolean { this.messages.push(msg.type); return true; } } describe('@lumino/widgets', () => { describe('SplitLayout', () => { describe('#constructor()', () => { it('should accept a renderer', () => { let layout = new SplitLayout({ renderer }); expect(layout).to.be.an.instanceof(SplitLayout); }); }); describe('#orientation', () => { it('should get the layout orientation for the split layout', () => { let layout = new SplitLayout({ renderer }); expect(layout.orientation).to.equal('horizontal'); }); it('should set the layout orientation for the split layout', () => { let layout = new SplitLayout({ renderer }); layout.orientation = 'vertical'; expect(layout.orientation).to.equal('vertical'); }); it('should set the orientation attribute of the parent widget', () => { let parent = new Widget(); let layout = new SplitLayout({ renderer }); parent.layout = layout; layout.orientation = 'vertical'; expect(parent.node.getAttribute('data-orientation')).to.equal( 'vertical' ); layout.orientation = 'horizontal'; expect(parent.node.getAttribute('data-orientation')).to.equal( 'horizontal' ); }); it('should post a fit request to the parent widget', done => { let layout = new LogSplitLayout({ renderer }); let parent = new Widget(); parent.layout = layout; layout.orientation = 'vertical'; requestAnimationFrame(() => { expect(layout.methods).to.contain('onFitRequest'); done(); }); }); it('should be a no-op if the value does not change', done => { let layout = new LogSplitLayout({ renderer }); let parent = new Widget(); parent.layout = layout; layout.orientation = 'horizontal'; requestAnimationFrame(() => { expect(layout.methods).to.not.contain('onFitRequest'); done(); }); }); }); describe('#spacing', () => { it('should get the inter-element spacing for the split layout', () => { let layout = new SplitLayout({ renderer }); expect(layout.spacing).to.equal(4); }); it('should set the inter-element spacing for the split layout', () => { let layout = new SplitLayout({ renderer }); layout.spacing = 10; expect(layout.spacing).to.equal(10); }); it('should post a fit rquest to the parent widget', done => { let layout = new LogSplitLayout({ renderer }); let parent = new Widget(); parent.layout = layout; layout.spacing = 10; requestAnimationFrame(() => { expect(layout.methods).to.contain('onFitRequest'); done(); }); }); it('should be a no-op if the value does not change', done => { let layout = new LogSplitLayout({ renderer }); let parent = new Widget(); parent.layout = layout; layout.spacing = 4; requestAnimationFrame(() => { expect(layout.methods).to.not.contain('onFitRequest'); done(); }); }); }); describe('#renderer', () => { it('should get the renderer for the layout', () => { let layout = new SplitLayout({ renderer }); expect(layout.renderer).to.equal(renderer); }); }); describe('#handles', () => { it('should be a read-only sequence of the split handles in the layout', () => { let layout = new SplitLayout({ renderer }); let widgets = [new Widget(), new Widget(), new Widget()]; each(widgets, w => { layout.addWidget(w); }); expect(every(layout.handles, h => h instanceof HTMLElement)); }); }); describe('#relativeSizes()', () => { it('should get the current sizes of the widgets in the layout', () => { let layout = new SplitLayout({ renderer }); let widgets = [new Widget(), new Widget(), new Widget()]; let parent = new Widget(); parent.layout = layout; each(widgets, w => { layout.addWidget(w); }); let sizes = layout.relativeSizes(); expect(sizes).to.deep.equal([1 / 3, 1 / 3, 1 / 3]); parent.dispose(); }); }); describe('#setRelativeSizes()', () => { it('should set the desired sizes for the widgets in the panel', () => { let layout = new SplitLayout({ renderer }); let widgets = [new Widget(), new Widget(), new Widget()]; let parent = new Widget(); parent.layout = layout; each(widgets, w => { layout.addWidget(w); }); layout.setRelativeSizes([10, 10, 10]); let sizes = layout.relativeSizes(); expect(sizes).to.deep.equal([10 / 30, 10 / 30, 10 / 30]); parent.dispose(); }); it('should ignore extra values', () => { let layout = new SplitLayout({ renderer }); let widgets = [new Widget(), new Widget(), new Widget()]; let parent = new Widget(); parent.layout = layout; each(widgets, w => { layout.addWidget(w); }); layout.setRelativeSizes([10, 15, 20, 20]); let sizes = layout.relativeSizes(); expect(sizes).to.deep.equal([10 / 45, 15 / 45, 20 / 45]); parent.dispose(); }); }); describe('#moveHandle()', () => { it('should set the offset position of a split handle', done => { let parent = new Widget(); let layout = new SplitLayout({ renderer }); let widgets = [new Widget(), new Widget(), new Widget()]; each(widgets, w => { layout.addWidget(w); }); each(widgets, w => { w.node.style.minHeight = '100px'; }); each(widgets, w => { w.node.style.minWidth = '100px'; }); parent.layout = layout; Widget.attach(parent, document.body); MessageLoop.flush(); let handle = layout.handles[1]; let left = handle.offsetLeft; layout.moveHandle(1, left + 20); requestAnimationFrame(() => { expect(handle.offsetLeft).to.not.equal(left); done(); }); }); }); describe('#init()', () => { it('should set the orientation attribute of the parent widget', () => { let parent = new Widget(); let layout = new LogSplitLayout({ renderer }); parent.layout = layout; expect(layout.methods).to.contain('init'); expect(parent.node.getAttribute('data-orientation')).to.equal( 'horizontal' ); }); it('should attach all widgets to the DOM', () => { let parent = new Widget(); Widget.attach(parent, document.body); let layout = new LogSplitLayout({ renderer }); let widgets = [new Widget(), new Widget(), new Widget()]; each(widgets, w => { layout.addWidget(w); }); parent.layout = layout; expect(every(widgets, w => w.parent === parent)).to.equal(true); expect(every(widgets, w => w.isAttached)).to.equal(true); parent.dispose(); }); }); describe('#attachWidget()', () => { it("should attach a widget to the parent's DOM node", () => { let layout = new LogSplitLayout({ renderer }); let parent = new Widget(); parent.layout = layout; let widget = new Widget(); layout.addWidget(widget); expect(layout.methods).to.contain('attachWidget'); expect(parent.node.contains(widget.node)).to.equal(true); expect(layout.handles.length).to.equal(1); }); it('should send before/after attach messages if the parent is attached', () => { let layout = new LogSplitLayout({ renderer }); let parent = new Widget(); let widget = new Widget(); let hook = new LogHook(); MessageLoop.installMessageHook(widget, hook); parent.layout = layout; Widget.attach(parent, document.body); layout.addWidget(widget); expect(hook.messages).to.contain('before-attach'); expect(hook.messages).to.contain('after-attach'); }); it('should post a layout request for the parent widget', done => { let layout = new LogSplitLayout({ renderer }); let parent = new Widget(); parent.layout = layout; let widget = new Widget(); Widget.attach(parent, document.body); layout.addWidget(widget); requestAnimationFrame(() => { expect(layout.methods).to.contain('onFitRequest'); done(); }); }); }); describe('#moveWidget()', () => { it("should move a widget in the parent's DOM node", () => { let layout = new LogSplitLayout({ renderer }); let widgets = [new Widget(), new Widget(), new Widget()]; let parent = new Widget(); parent.layout = layout; each(widgets, w => { layout.addWidget(w); }); let widget = widgets[0]; let handle = layout.handles[0]; layout.insertWidget(2, widget); expect(layout.methods).to.contain('moveWidget'); expect(layout.handles[2]).to.equal(handle); expect(layout.widgets[2]).to.equal(widget); }); it('should post a a layout request to the parent', done => { let layout = new LogSplitLayout({ renderer }); let widgets = [new Widget(), new Widget(), new Widget()]; let parent = new Widget(); parent.layout = layout; each(widgets, w => { layout.addWidget(w); }); let widget = widgets[0]; layout.insertWidget(2, widget); requestAnimationFrame(() => { expect(layout.methods).to.contain('onFitRequest'); done(); }); }); }); describe('#detachWidget()', () => { it("should detach a widget from the parent's DOM node", () => { let layout = new LogSplitLayout({ renderer }); let widget = new Widget(); let parent = new Widget(); parent.layout = layout; layout.addWidget(widget); layout.removeWidget(widget); expect(layout.methods).to.contain('detachWidget'); expect(parent.node.contains(widget.node)).to.equal(false); parent.dispose(); }); it('should send before/after detach message if the parent is attached', () => { let layout = new LogSplitLayout({ renderer }); let parent = new Widget(); let widget = new Widget(); let hook = new LogHook(); MessageLoop.installMessageHook(widget, hook); parent.layout = layout; layout.addWidget(widget); Widget.attach(parent, document.body); layout.removeWidget(widget); expect(layout.methods).to.contain('detachWidget'); expect(hook.messages).to.contain('before-detach'); expect(hook.messages).to.contain('after-detach'); parent.dispose(); }); it('should post a a layout request to the parent', done => { let layout = new LogSplitLayout({ renderer }); let widget = new Widget(); let parent = new Widget(); parent.layout = layout; layout.addWidget(widget); Widget.attach(parent, document.body); layout.removeWidget(widget); requestAnimationFrame(() => { expect(layout.methods).to.contain('onFitRequest'); parent.dispose(); done(); }); }); }); describe('#onAfterShow()', () => { it('should post an update to the parent', done => { let layout = new LogSplitLayout({ renderer }); let parent = new Widget(); parent.layout = layout; parent.hide(); Widget.attach(parent, document.body); parent.show(); expect(layout.methods).to.contain('onAfterShow'); requestAnimationFrame(() => { expect(layout.methods).to.contain('onUpdateRequest'); parent.dispose(); done(); }); }); }); describe('#onAfterAttach()', () => { it('should post a layout request to the parent', done => { let layout = new LogSplitLayout({ renderer }); let parent = new Widget(); parent.layout = layout; Widget.attach(parent, document.body); expect(layout.methods).to.contain('onAfterAttach'); requestAnimationFrame(() => { expect(layout.methods).to.contain('onFitRequest'); parent.dispose(); done(); }); }); }); describe('#onChildShown()', () => { it('should post a fit request to the parent', done => { let parent = new Widget(); let layout = new LogSplitLayout({ renderer }); parent.layout = layout; let widgets = [new Widget(), new Widget(), new Widget()]; widgets[0].hide(); each(widgets, w => { layout.addWidget(w); }); Widget.attach(parent, document.body); widgets[0].show(); expect(layout.methods).to.contain('onChildShown'); requestAnimationFrame(() => { expect(layout.methods).to.contain('onFitRequest'); parent.dispose(); done(); }); }); }); describe('#onChildHidden()', () => { it('should post a fit request to the parent', done => { let parent = new Widget(); let layout = new LogSplitLayout({ renderer }); parent.layout = layout; let widgets = [new Widget(), new Widget(), new Widget()]; each(widgets, w => { layout.addWidget(w); }); Widget.attach(parent, document.body); widgets[0].hide(); expect(layout.methods).to.contain('onChildHidden'); requestAnimationFrame(() => { expect(layout.methods).to.contain('onFitRequest'); parent.dispose(); done(); }); }); }); describe('#onResize', () => { it('should be called when a resize event is sent to the parent', () => { let parent = new Widget(); let layout = new LogSplitLayout({ renderer }); parent.layout = layout; let widgets = [new Widget(), new Widget(), new Widget()]; each(widgets, w => { layout.addWidget(w); }); Widget.attach(parent, document.body); MessageLoop.sendMessage(parent, Widget.ResizeMessage.UnknownSize); expect(layout.methods).to.contain('onResize'); parent.dispose(); }); }); describe('.getStretch()', () => { it('should get the split layout stretch factor for the given widget', () => { let widget = new Widget(); expect(SplitLayout.getStretch(widget)).to.equal(0); }); }); describe('.setStretch()', () => { it('should set the split layout stretch factor for the given widget', () => { let widget = new Widget(); SplitLayout.setStretch(widget, 10); expect(SplitLayout.getStretch(widget)).to.equal(10); }); it('should post a fit request to the parent', done => { let parent = new Widget(); let widget = new Widget(); let layout = new LogSplitLayout({ renderer }); parent.layout = layout; layout.addWidget(widget); SplitLayout.setStretch(widget, 10); requestAnimationFrame(() => { expect(layout.methods).to.contain('onFitRequest'); done(); }); }); }); }); }); lumino-2021.12.13/packages/widgets/tests/src/splitpanel.spec.ts000066400000000000000000000322461415564225700242610ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { expect } from 'chai'; import { generate, simulate } from 'simulate-event'; import { each, every } from '@lumino/algorithm'; import { MessageLoop } from '@lumino/messaging'; import { SplitLayout, SplitPanel, Widget } from '@lumino/widgets'; const renderer: SplitPanel.IRenderer = { createHandle: () => document.createElement('div') }; class LogSplitPanel extends SplitPanel { events: string[] = []; handleEvent(event: Event): void { super.handleEvent(event); this.events.push(event.type); } } describe('@lumino/widgets', () => { describe('SplitPanel', () => { describe('#constructor()', () => { it('should accept no arguments', () => { let panel = new SplitPanel(); expect(panel).to.be.an.instanceof(SplitPanel); }); it('should accept options', () => { let panel = new SplitPanel({ orientation: 'vertical', spacing: 5, renderer }); expect(panel.orientation).to.equal('vertical'); expect(panel.spacing).to.equal(5); expect(panel.renderer).to.equal(renderer); }); it('should accept a layout option', () => { let layout = new SplitLayout({ renderer }); let panel = new SplitPanel({ layout }); expect(panel.layout).to.equal(layout); }); it('should ignore other options if a layout is given', () => { let ignored = Object.create(renderer); let layout = new SplitLayout({ renderer }); let panel = new SplitPanel({ layout, orientation: 'vertical', spacing: 5, renderer: ignored }); expect(panel.layout).to.equal(layout); expect(panel.orientation).to.equal('horizontal'); expect(panel.spacing).to.equal(4); expect(panel.renderer).to.equal(renderer); }); it('should add the `lm-SplitPanel` class', () => { let panel = new SplitPanel(); expect(panel.hasClass('lm-SplitPanel')).to.equal(true); }); }); describe('#dispose()', () => { it('should dispose of the resources held by the panel', () => { let panel = new LogSplitPanel(); let layout = panel.layout as SplitLayout; let widgets = [new Widget(), new Widget(), new Widget()]; each(widgets, w => { panel.addWidget(w); }); Widget.attach(panel, document.body); simulate(layout.handles[0], 'mousedown'); expect(panel.events).to.contain('mousedown'); simulate(panel.node, 'keydown'); expect(panel.events).to.contain('keydown'); let node = panel.node; panel.dispose(); expect(every(widgets, w => w.isDisposed)); simulate(node, 'contextmenu'); expect(panel.events).to.not.contain('contextmenu'); }); }); describe('#orientation', () => { it('should get the layout orientation for the split panel', () => { let panel = new SplitPanel(); expect(panel.orientation).to.equal('horizontal'); }); it('should set the layout orientation for the split panel', () => { let panel = new SplitPanel(); panel.orientation = 'vertical'; expect(panel.orientation).to.equal('vertical'); }); }); describe('#spacing', () => { it('should default to `4`', () => { let panel = new SplitPanel(); expect(panel.spacing).to.equal(4); }); it('should set the spacing for the panel', () => { let panel = new SplitPanel(); panel.spacing = 10; expect(panel.spacing).to.equal(10); }); }); describe('#renderer', () => { it('should get the renderer for the panel', () => { let panel = new SplitPanel({ renderer }); expect(panel.renderer).to.equal(renderer); }); }); describe('#handles', () => { it('should get the read-only sequence of the split handles in the panel', () => { let panel = new SplitPanel(); let widgets = [new Widget(), new Widget(), new Widget()]; each(widgets, w => { panel.addWidget(w); }); expect(panel.handles.length).to.equal(3); }); }); describe('#relativeSizes()', () => { it('should get the current sizes of the widgets in the panel', () => { let panel = new SplitPanel(); let widgets = [new Widget(), new Widget(), new Widget()]; each(widgets, w => { panel.addWidget(w); }); let sizes = panel.relativeSizes(); expect(sizes).to.deep.equal([1 / 3, 1 / 3, 1 / 3]); }); }); describe('#setRelativeSizes()', () => { it('should set the desired sizes for the widgets in the panel', () => { let panel = new SplitPanel(); let widgets = [new Widget(), new Widget(), new Widget()]; each(widgets, w => { panel.addWidget(w); }); panel.setRelativeSizes([10, 20, 30]); let sizes = panel.relativeSizes(); expect(sizes).to.deep.equal([10 / 60, 20 / 60, 30 / 60]); }); it('should ignore extra values', () => { let panel = new SplitPanel(); let widgets = [new Widget(), new Widget(), new Widget()]; each(widgets, w => { panel.addWidget(w); }); panel.setRelativeSizes([10, 30, 40, 20]); let sizes = panel.relativeSizes(); expect(sizes).to.deep.equal([10 / 80, 30 / 80, 40 / 80]); }); }); describe('#handleEvent()', () => { let panel: LogSplitPanel; let layout: SplitLayout; beforeEach(() => { panel = new LogSplitPanel(); layout = panel.layout as SplitLayout; let widgets = [new Widget(), new Widget(), new Widget()]; each(widgets, w => { panel.addWidget(w); }); panel.setRelativeSizes([10, 10, 10, 20]); Widget.attach(panel, document.body); MessageLoop.flush(); }); afterEach(() => { panel.dispose(); }); context('mousedown', () => { it('should attach other event listeners', () => { simulate(layout.handles[0], 'mousedown'); expect(panel.events).to.contain('mousedown'); simulate(document.body, 'mousemove'); expect(panel.events).to.contain('mousemove'); simulate(document.body, 'keydown'); expect(panel.events).to.contain('keydown'); simulate(document.body, 'contextmenu'); expect(panel.events).to.contain('contextmenu'); simulate(document.body, 'mouseup'); expect(panel.events).to.contain('mouseup'); }); it('should be a no-op if it is not the left button', () => { simulate(layout.handles[0], 'mousedown', { button: 1 }); expect(panel.events).to.contain('mousedown'); simulate(document.body, 'mousemove'); expect(panel.events).to.not.contain('mousemove'); }); }); context('mousemove', () => { it('should move the handle right', done => { let handle = layout.handles[1]; let rect = handle.getBoundingClientRect(); simulate(handle, 'mousedown'); simulate(document.body, 'mousemove', { clientX: rect.left + 10, clientY: rect.top }); requestAnimationFrame(() => { let newRect = handle.getBoundingClientRect(); expect(newRect.left).to.not.equal(rect.left); done(); }); }); it('should move the handle down', done => { panel.orientation = 'vertical'; each(panel.widgets, w => { w.node.style.minHeight = '20px'; }); let handle = layout.handles[1]; let rect = handle.getBoundingClientRect(); simulate(handle, 'mousedown'); simulate(document.body, 'mousemove', { clientX: rect.left, clientY: rect.top - 2 }); requestAnimationFrame(() => { let newRect = handle.getBoundingClientRect(); expect(newRect.top).to.not.equal(rect.top); done(); }); }); }); context('mouseup', () => { it('should remove the event listeners', () => { simulate(layout.handles[0], 'mousedown'); expect(panel.events).to.contain('mousedown'); simulate(document.body, 'mouseup'); expect(panel.events).to.contain('mouseup'); simulate(document.body, 'mousemove'); expect(panel.events).to.not.contain('mousemove'); simulate(document.body, 'keydown'); expect(panel.events).to.not.contain('keydown'); simulate(document.body, 'contextmenu'); expect(panel.events).to.not.contain('contextmenu'); }); it('should be a no-op if not the left button', () => { simulate(layout.handles[0], 'mousedown'); expect(panel.events).to.contain('mousedown'); simulate(document.body, 'mouseup', { button: 1 }); expect(panel.events).to.contain('mouseup'); simulate(document.body, 'mousemove'); expect(panel.events).to.contain('mousemove'); }); }); context('keydown', () => { it('should release the mouse if `Escape` is pressed', () => { simulate(layout.handles[0], 'mousedown'); simulate(panel.node, 'keydown', { keyCode: 27 }); expect(panel.events).to.contain('keydown'); simulate(panel.node, 'mousemove'); expect(panel.events).to.not.contain('mousemove'); }); }); context('contextmenu', () => { it('should prevent events during drag', () => { simulate(layout.handles[0], 'mousedown'); let evt = generate('contextmenu'); let cancelled = !document.body.dispatchEvent(evt); expect(cancelled).to.equal(true); expect(panel.events).to.contain('contextmenu'); }); }); }); describe('#onAfterAttach()', () => { it('should attach a mousedown listener to the node', () => { let panel = new LogSplitPanel(); Widget.attach(panel, document.body); simulate(panel.node, 'mousedown'); expect(panel.events).to.contain('mousedown'); panel.dispose(); }); }); describe('#onBeforeDetach()', () => { it('should remove all listeners', () => { let panel = new LogSplitPanel(); Widget.attach(panel, document.body); simulate(panel.node, 'mousedown'); expect(panel.events).to.contain('mousedown'); Widget.detach(panel); panel.events = []; simulate(panel.node, 'mousedown'); expect(panel.events).to.not.contain('mousedown'); simulate(document.body, 'keyup'); expect(panel.events).to.not.contain('keyup'); }); }); describe('#onChildAdded()', () => { it('should add a class to the child widget', () => { let panel = new SplitPanel(); let widget = new Widget(); panel.addWidget(widget); expect(widget.hasClass('lm-SplitPanel-child')).to.equal(true); }); }); describe('#onChildRemoved()', () => { it('should remove a class to the child widget', () => { let panel = new SplitPanel(); let widget = new Widget(); panel.addWidget(widget); widget.parent = null; expect(widget.hasClass('lm-SplitPanel-child')).to.equal(false); }); }); describe('.Renderer()', () => { describe('#createHandle()', () => { it('should create a new handle node', () => { let renderer = new SplitPanel.Renderer(); let node1 = renderer.createHandle(); let node2 = renderer.createHandle(); expect(node1).to.be.an.instanceof(HTMLElement); expect(node2).to.be.an.instanceof(HTMLElement); expect(node1).to.not.equal(node2); }); it('should add the "lm-SplitPanel-handle" class', () => { let renderer = new SplitPanel.Renderer(); let node = renderer.createHandle(); expect(node.classList.contains('lm-SplitPanel-handle')).to.equal( true ); }); }); }); describe('.defaultRenderer', () => { it('should be an instance of `Renderer`', () => { expect(SplitPanel.defaultRenderer).to.be.an.instanceof( SplitPanel.Renderer ); }); }); describe('.getStretch()', () => { it('should get the split panel stretch factor for the given widget', () => { let widget = new Widget(); expect(SplitPanel.getStretch(widget)).to.equal(0); }); }); describe('.setStretch()', () => { it('should set the split panel stretch factor for the given widget', () => { let widget = new Widget(); SplitPanel.setStretch(widget, 10); expect(SplitPanel.getStretch(widget)).to.equal(10); }); }); }); }); lumino-2021.12.13/packages/widgets/tests/src/stackedlayout.spec.ts000066400000000000000000000232171415564225700247600ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { expect } from 'chai'; import { each } from '@lumino/algorithm'; import { IMessageHandler, IMessageHook, Message, MessageLoop } from '@lumino/messaging'; import { StackedLayout, Widget } from '@lumino/widgets'; class LogHook implements IMessageHook { messages: string[] = []; messageHook(target: IMessageHandler, msg: Message): boolean { this.messages.push(msg.type); return true; } } class LogStackedLayout extends StackedLayout { methods: string[] = []; protected init(): void { super.init(); this.methods.push('init'); } protected attachWidget(index: number, widget: Widget): void { super.attachWidget(index, widget); this.methods.push('attachWidget'); } protected moveWidget( fromIndex: number, toIndex: number, widget: Widget ): void { super.moveWidget(fromIndex, toIndex, widget); this.methods.push('moveWidget'); } protected detachWidget(index: number, widget: Widget): void { super.detachWidget(index, widget); this.methods.push('detachWidget'); } protected onAfterShow(msg: Message): void { super.onAfterShow(msg); this.methods.push('onAfterShow'); } protected onAfterAttach(msg: Message): void { super.onAfterAttach(msg); this.methods.push('onAfterAttach'); } protected onChildShown(msg: Widget.ChildMessage): void { super.onChildShown(msg); this.methods.push('onChildShown'); } protected onChildHidden(msg: Widget.ChildMessage): void { super.onChildHidden(msg); this.methods.push('onChildHidden'); } protected onResize(msg: Widget.ResizeMessage): void { super.onResize(msg); this.methods.push('onResize'); } protected onUpdateRequest(msg: Message): void { super.onUpdateRequest(msg); this.methods.push('onUpdateRequest'); } protected onFitRequest(msg: Message): void { super.onFitRequest(msg); this.methods.push('onFitRequest'); } } describe('@lumino/widgets', () => { describe('StackedLayout', () => { describe('#attachWidget()', () => { it("should attach a widget to the parent's DOM node", () => { let layout = new LogStackedLayout(); let parent = new Widget(); let widget = new Widget(); parent.layout = layout; layout.addWidget(widget); expect(layout.methods).to.contain('attachWidget'); expect(parent.node.contains(widget.node)).to.equal(true); }); it('should send before/after attach messages if the parent is attached', () => { let layout = new LogStackedLayout(); let parent = new Widget(); let widget = new Widget(); let hook = new LogHook(); MessageLoop.installMessageHook(widget, hook); parent.layout = layout; Widget.attach(parent, document.body); layout.addWidget(widget); expect(hook.messages).to.contain('before-attach'); expect(hook.messages).to.contain('after-attach'); }); it('should post a fit request for the parent widget', done => { let layout = new LogStackedLayout(); let parent = new Widget(); let widget = new Widget(); parent.layout = layout; Widget.attach(parent, document.body); layout.addWidget(widget); requestAnimationFrame(() => { expect(layout.methods).to.contain('onFitRequest'); done(); }); }); }); describe('#moveWidget()', () => { it("should move a widget in the parent's DOM node", () => { let layout = new LogStackedLayout(); let widgets = [new Widget(), new Widget(), new Widget()]; let parent = new Widget(); parent.layout = layout; each(widgets, w => { layout.addWidget(w); }); layout.insertWidget(2, widgets[0]); expect(layout.methods).to.contain('moveWidget'); expect(layout.widgets[2]).to.equal(widgets[0]); }); it('should post an update request to the parent', done => { let layout = new LogStackedLayout(); let widgets = [new Widget(), new Widget(), new Widget()]; let parent = new Widget(); parent.layout = layout; each(widgets, w => { layout.addWidget(w); }); layout.insertWidget(2, widgets[0]); requestAnimationFrame(() => { expect(layout.methods).to.contain('onUpdateRequest'); done(); }); }); }); describe('#detachWidget()', () => { it("should detach a widget from the parent's DOM node", () => { let layout = new LogStackedLayout(); let widget = new Widget(); let parent = new Widget(); parent.layout = layout; layout.addWidget(widget); layout.removeWidget(widget); expect(layout.methods).to.contain('detachWidget'); expect(parent.node.contains(widget.node)).to.equal(false); parent.dispose(); }); it('should send before/after detach message if the parent is attached', () => { let layout = new LogStackedLayout(); let parent = new Widget(); let widget = new Widget(); let hook = new LogHook(); MessageLoop.installMessageHook(widget, hook); parent.layout = layout; layout.addWidget(widget); Widget.attach(parent, document.body); layout.removeWidget(widget); expect(layout.methods).to.contain('detachWidget'); expect(hook.messages).to.contain('before-detach'); expect(hook.messages).to.contain('after-detach'); parent.dispose(); }); it('should post a a layout request to the parent', done => { let layout = new LogStackedLayout(); let parent = new Widget(); let widget = new Widget(); parent.layout = layout; layout.addWidget(widget); Widget.attach(parent, document.body); layout.removeWidget(widget); requestAnimationFrame(() => { expect(layout.methods).to.contain('onFitRequest'); parent.dispose(); done(); }); }); it('should reset the z-index for the widget', done => { let layout = new LogStackedLayout(); let parent = new Widget(); let widget1 = new Widget(); let widget2 = new Widget(); parent.layout = layout; layout.addWidget(widget1); layout.addWidget(widget2); Widget.attach(parent, document.body); requestAnimationFrame(() => { // string casts are required for IE expect(`${widget1.node.style.zIndex}`).to.equal('0'); expect(`${widget2.node.style.zIndex}`).to.equal('1'); layout.removeWidget(widget1); expect(`${widget1.node.style.zIndex}`).to.equal(''); expect(`${widget2.node.style.zIndex}`).to.equal('1'); layout.removeWidget(widget2); expect(`${widget2.node.style.zIndex}`).to.equal(''); parent.dispose(); done(); }); }); }); describe('#onAfterShow()', () => { it('should post an update to the parent', done => { let layout = new LogStackedLayout(); let parent = new Widget(); parent.layout = layout; parent.hide(); Widget.attach(parent, document.body); parent.show(); expect(layout.methods).to.contain('onAfterShow'); requestAnimationFrame(() => { expect(layout.methods).to.contain('onUpdateRequest'); parent.dispose(); done(); }); }); }); describe('#onAfterAttach()', () => { it('should post a layout request to the parent', done => { let layout = new LogStackedLayout(); let parent = new Widget(); parent.layout = layout; Widget.attach(parent, document.body); expect(layout.methods).to.contain('onAfterAttach'); requestAnimationFrame(() => { expect(layout.methods).to.contain('onFitRequest'); parent.dispose(); done(); }); }); }); describe('#onChildShown()', () => { it('should post or send a fit request to the parent', done => { let parent = new Widget(); let layout = new LogStackedLayout(); parent.layout = layout; let widgets = [new Widget(), new Widget(), new Widget()]; widgets[0].hide(); each(widgets, w => { layout.addWidget(w); }); Widget.attach(parent, document.body); widgets[0].show(); expect(layout.methods).to.contain('onChildShown'); requestAnimationFrame(() => { expect(layout.methods).to.contain('onFitRequest'); parent.dispose(); done(); }); }); }); describe('#onChildHidden()', () => { it('should post or send a fit request to the parent', done => { let parent = new Widget(); let layout = new LogStackedLayout(); parent.layout = layout; let widgets = [new Widget(), new Widget(), new Widget()]; each(widgets, w => { layout.addWidget(w); }); Widget.attach(parent, document.body); widgets[0].hide(); expect(layout.methods).to.contain('onChildHidden'); requestAnimationFrame(() => { expect(layout.methods).to.contain('onFitRequest'); parent.dispose(); done(); }); }); }); }); }); lumino-2021.12.13/packages/widgets/tests/src/stackedpanel.spec.ts000066400000000000000000000071741415564225700245460ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { expect } from 'chai'; import { StackedLayout, StackedPanel, Widget } from '@lumino/widgets'; describe('@lumino/widgets', () => { describe('StackedPanel', () => { describe('#constructor()', () => { it('should take no arguments', () => { let panel = new StackedPanel(); expect(panel).to.be.an.instanceof(StackedPanel); }); it('should take options', () => { let layout = new StackedLayout(); let panel = new StackedPanel({ layout }); expect(panel.layout).to.equal(layout); }); it('should add the `lm-StackedPanel` class', () => { let panel = new StackedPanel(); expect(panel.hasClass('lm-StackedPanel')).to.equal(true); }); }); describe('hiddenMode', () => { let panel: StackedPanel; let widgets: Widget[] = []; beforeEach(() => { panel = new StackedPanel(); // Create two stacked widgets widgets.push(new Widget()); panel.addWidget(widgets[0]); widgets.push(new Widget()); panel.addWidget(widgets[1]); }); afterEach(() => { panel.dispose(); }); it("should be 'display' mode by default", () => { expect(panel.hiddenMode).to.equal(Widget.HiddenMode.Display); }); it("should switch to 'scale'", () => { panel.hiddenMode = Widget.HiddenMode.Scale; expect(widgets[0].hiddenMode).to.equal(Widget.HiddenMode.Scale); expect(widgets[1].hiddenMode).to.equal(Widget.HiddenMode.Scale); }); it("should switch to 'display'", () => { widgets[0].hiddenMode = Widget.HiddenMode.Scale; panel.hiddenMode = Widget.HiddenMode.Scale; panel.hiddenMode = Widget.HiddenMode.Display; expect(widgets[0].hiddenMode).to.equal(Widget.HiddenMode.Display); expect(widgets[1].hiddenMode).to.equal(Widget.HiddenMode.Display); }); it("should not set 'scale' if only one widget", () => { panel.layout!.removeWidget(widgets[1]); panel.hiddenMode = Widget.HiddenMode.Scale; expect(widgets[0].hiddenMode).to.equal(Widget.HiddenMode.Display); }); }); describe('#widgetRemoved', () => { it('should be emitted when a widget is removed from a stacked panel', () => { let panel = new StackedPanel(); let widget = new Widget(); panel.addWidget(widget); panel.widgetRemoved.connect((sender, args) => { expect(sender).to.equal(panel); expect(args).to.equal(widget); }); widget.parent = null; }); }); describe('#onChildAdded()', () => { it('should add a class to the child widget', () => { let panel = new StackedPanel(); let widget = new Widget(); panel.addWidget(widget); expect(widget.hasClass('lm-StackedPanel-child')).to.equal(true); }); }); describe('#onChildRemoved()', () => { it('should remove a class to the child widget', () => { let panel = new StackedPanel(); let widget = new Widget(); panel.addWidget(widget); widget.parent = null; expect(widget.hasClass('lm-StackedPanel-child')).to.equal(false); }); }); }); }); lumino-2021.12.13/packages/widgets/tests/src/tabbar.spec.ts000066400000000000000000001462441415564225700233450ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { expect } from 'chai'; import { generate, simulate } from 'simulate-event'; import { each, range } from '@lumino/algorithm'; import { Message, MessageLoop } from '@lumino/messaging'; import { TabBar, Title, Widget } from '@lumino/widgets'; import { VirtualDOM, VirtualElement } from '@lumino/virtualdom'; class LogTabBar extends TabBar { events: string[] = []; methods: string[] = []; handleEvent(event: Event): void { super.handleEvent(event); this.events.push(event.type); } protected onBeforeAttach(msg: Message): void { super.onBeforeAttach(msg); this.methods.push('onBeforeAttach'); } protected onAfterDetach(msg: Message): void { super.onAfterDetach(msg); this.methods.push('onAfterDetach'); } protected onUpdateRequest(msg: Message): void { super.onUpdateRequest(msg); this.methods.push('onUpdateRequest'); } } function populateBar(bar: TabBar): void { // Add some tabs with labels. each(range(3), i => { let widget = new Widget(); widget.title.label = `Test - ${i}`; widget.title.closable = true; bar.addTab(widget.title); }); // Force the tabs to render MessageLoop.sendMessage(bar, Widget.Msg.UpdateRequest); // Add the close icon content. each(range(3), i => { let tab = bar.contentNode.children[i]; let icon = tab.querySelector(bar.renderer.closeIconSelector); icon!.textContent = 'X'; }); } type Direction = 'left' | 'right' | 'up' | 'down'; function startDrag( bar: LogTabBar, index = 0, direction: Direction = 'right' ): void { bar.tabsMovable = true; let tab = bar.contentNode.children[index] as HTMLElement; bar.currentIndex = index; // Force an update. MessageLoop.sendMessage(bar, Widget.Msg.UpdateRequest); simulateOnNode(tab, 'mousedown'); let called = true; bar.tabDetachRequested.connect((sender, args) => { called = true; }); let rect = bar.contentNode.getBoundingClientRect(); let args: any; switch (direction) { case 'left': args = { clientX: rect.left - 200, clientY: rect.top }; break; case 'up': args = { clientX: rect.left, clientY: rect.top - 200 }; break; case 'down': args = { clientX: rect.left, clientY: rect.bottom + 200 }; break; default: args = { clientX: rect.right + 200, clientY: rect.top }; break; } simulate(document.body, 'mousemove', args); expect(called).to.equal(true); bar.events = []; } function simulateOnNode(node: Element, eventName: string): void { let rect = node.getBoundingClientRect(); simulate(node, eventName, { clientX: rect.left + 1, clientY: rect.top }); } describe('@lumino/widgets', () => { describe('TabBar', () => { let bar: LogTabBar; beforeEach(() => { bar = new LogTabBar(); Widget.attach(bar, document.body); }); afterEach(() => { bar.dispose(); }); describe('#constructor()', () => { it('should take no arguments', () => { let newBar = new TabBar(); expect(newBar).to.be.an.instanceof(TabBar); }); it('should take an options argument', () => { let renderer = new TabBar.Renderer(); let newBar = new TabBar({ orientation: 'horizontal', tabsMovable: true, allowDeselect: true, addButtonEnabled: true, insertBehavior: 'select-tab', removeBehavior: 'select-previous-tab', renderer }); expect(newBar).to.be.an.instanceof(TabBar); expect(newBar.tabsMovable).to.equal(true); expect(newBar.renderer).to.equal(renderer); expect(newBar.addButtonEnabled).to.equal(true); }); it('should add the `lm-TabBar` class', () => { let newBar = new TabBar(); expect(newBar.hasClass('lm-TabBar')).to.equal(true); }); }); describe('#dispose()', () => { it('should dispose of the resources held by the widget', () => { bar.dispose(); expect(bar.isDisposed).to.equal(true); bar.dispose(); expect(bar.isDisposed).to.equal(true); }); }); describe('#currentChanged', () => { it('should be emitted when the current tab is changed', () => { populateBar(bar); let called = false; let titles = bar.titles; bar.currentChanged.connect((sender, args) => { expect(sender).to.equal(bar); expect(args.previousIndex).to.equal(0); expect(args.previousTitle).to.equal(titles[0]); expect(args.currentIndex).to.equal(1); expect(args.currentTitle).to.equal(titles[1]); called = true; }); bar.currentTitle = titles[1]; expect(called).to.equal(true); }); it('should not be emitted when another tab is inserted', () => { populateBar(bar); let called = false; bar.currentChanged.connect((sender, args) => { called = true; }); let widget = new Widget(); bar.insertTab(0, widget.title); expect(called).to.equal(false); }); it('should not be emitted when another tab is removed', () => { populateBar(bar); let called = false; bar.currentIndex = 1; bar.currentChanged.connect((sender, args) => { called = true; }); bar.removeTab(bar.titles[0]); expect(called).to.equal(false); }); it('should not be emitted when the current tab is moved', () => { populateBar(bar); let called = false; bar.currentChanged.connect((sender, args) => { called = true; }); bar.insertTab(2, bar.titles[0]); expect(called).to.equal(false); }); }); describe('#tabMoved', () => { it('should be emitted when a tab is moved right by the user', done => { populateBar(bar); let titles = bar.titles.slice(); bar.tabMoved.connect((sender, args) => { expect(sender).to.equal(bar); expect(args.fromIndex).to.equal(0); expect(args.toIndex).to.equal(2); expect(args.title).to.equal(titles[0]); done(); }); startDrag(bar); simulate(document.body, 'mouseup'); }); it('should be emitted when a tab is moved left by the user', done => { populateBar(bar); let titles = bar.titles.slice(); bar.tabMoved.connect((sender, args) => { expect(sender).to.equal(bar); expect(args.fromIndex).to.equal(2); expect(args.toIndex).to.equal(0); expect(args.title).to.equal(titles[2]); done(); }); startDrag(bar, 2, 'left'); simulate(document.body, 'mouseup'); }); it('should not be emitted when a tab is moved programmatically', () => { populateBar(bar); let called = false; bar.tabMoved.connect((sender, args) => { called = true; }); bar.insertTab(2, bar.titles[0]); expect(called).to.equal(false); }); }); describe('#tabActivateRequested', () => { let tab: HTMLElement; beforeEach(() => { populateBar(bar); bar.tabsMovable = false; tab = bar.contentNode.getElementsByClassName( 'lm-TabBar-tab' )[2] as HTMLElement; }); it('should be emitted when a tab is left pressed by the user', () => { let called = false; bar.currentIndex = 0; // Force an update. MessageLoop.sendMessage(bar, Widget.Msg.UpdateRequest); bar.tabActivateRequested.connect((sender, args) => { expect(sender).to.equal(bar); expect(args.index).to.equal(2); expect(args.title).to.equal(bar.titles[2]); called = true; }); simulateOnNode(tab, 'mousedown'); expect(called).to.equal(true); }); it('should make the tab current and emit the `currentChanged` signal', () => { let called = 0; bar.currentIndex = 1; // Force an update. MessageLoop.sendMessage(bar, Widget.Msg.UpdateRequest); bar.tabActivateRequested.connect(() => { called++; }); bar.currentChanged.connect(() => { called++; }); simulateOnNode(tab, 'mousedown'); expect(bar.currentIndex).to.equal(2); expect(called).to.equal(2); }); it('should emit even if the pressed tab is the current tab', () => { let called = false; bar.currentIndex = 2; // Force an update. MessageLoop.sendMessage(bar, Widget.Msg.UpdateRequest); bar.tabActivateRequested.connect(() => { called = true; }); simulateOnNode(tab, 'mousedown'); expect(bar.currentIndex).to.equal(2); expect(called).to.equal(true); }); }); describe('#tabCloseRequested', () => { let tab: Element; let closeIcon: Element; beforeEach(() => { populateBar(bar); bar.currentIndex = 0; tab = bar.contentNode.children[0]; closeIcon = tab.querySelector(bar.renderer.closeIconSelector)!; }); it('should be emitted when a tab close icon is left clicked', () => { let called = false; let rect = closeIcon.getBoundingClientRect(); bar.tabCloseRequested.connect((sender, args) => { expect(sender).to.equal(bar); expect(args.index).to.equal(0); expect(args.title).to.equal(bar.titles[0]); called = true; }); simulate(closeIcon, 'mousedown', { clientX: rect.left, clientY: rect.top, button: 0 }); simulate(closeIcon, 'mouseup', { clientX: rect.left, clientY: rect.top, button: 0 }); expect(called).to.equal(true); }); it('should be emitted when a tab is middle clicked', () => { let called = false; let rect = tab.getBoundingClientRect(); bar.tabCloseRequested.connect((sender, args) => { expect(sender).to.equal(bar); expect(args.index).to.equal(0); expect(args.title).to.equal(bar.titles[0]); called = true; }); simulate(tab, 'mousedown', { clientX: rect.left, clientY: rect.top, button: 1 }); simulate(tab, 'mouseup', { clientX: rect.left, clientY: rect.top, button: 1 }); expect(called).to.equal(true); }); it('should not be emitted if the tab title is not `closable`', () => { let called = false; let title = bar.titles[0]; title.closable = false; bar.tabCloseRequested.connect((sender, args) => { expect(sender).to.equal(bar); expect(args.index).to.equal(0); expect(args.title).to.equal(bar.titles[0]); called = true; }); let rect1 = closeIcon.getBoundingClientRect(); let rect2 = tab.getBoundingClientRect(); simulate(closeIcon, 'mousedown', { clientX: rect1.left, clientY: rect1.top, button: 0 }); simulate(closeIcon, 'mouseup', { clientX: rect1.left, clientY: rect1.top, button: 0 }); simulate(tab, 'mousedown', { clientX: rect2.left, clientY: rect2.top, button: 1 }); simulate(tab, 'mouseup', { clientX: rect2.left, clientY: rect2.top, button: 1 }); expect(called).to.equal(false); }); }); describe('#addRequested', () => { let addButton: Element; beforeEach(() => { populateBar(bar); bar.currentIndex = 0; addButton = bar.addButtonNode; }); it('should be emitted when the add button is clicked', () => { bar.addButtonEnabled = true; let called = false; let rect = addButton.getBoundingClientRect(); bar.addRequested.connect((sender, args) => { expect(sender).to.equal(bar); expect(args).to.equal(undefined); called = true; }); simulate(addButton, 'mousedown', { clientX: rect.left, clientY: rect.top, button: 0 }); simulate(addButton, 'mouseup', { clientX: rect.left, clientY: rect.top, button: 0 }); expect(called).to.equal(true); }); it('should not be emitted if addButtonEnabled is `false`', () => { bar.addButtonEnabled = false; let called = false; let rect = addButton.getBoundingClientRect(); bar.addRequested.connect((sender, args) => { expect(sender).to.equal(bar); expect(args).to.equal(undefined); called = true; }); simulate(addButton, 'mousedown', { clientX: rect.left, clientY: rect.top, button: 0 }); simulate(addButton, 'mouseup', { clientX: rect.left, clientY: rect.top, button: 0 }); expect(called).to.equal(false); }); }); describe('#tabDetachRequested', () => { let tab: HTMLElement; beforeEach(() => { populateBar(bar); bar.tabsMovable = true; tab = bar.contentNode.children[bar.currentIndex] as HTMLElement; }); it('should be emitted when a tab is dragged beyond the detach threshold', () => { simulateOnNode(tab, 'mousedown'); let called = false; bar.tabDetachRequested.connect((sender, args) => { expect(sender).to.equal(bar); expect(args.index).to.equal(0); expect(args.title).to.equal(bar.titles[0]); expect(args.clientX).to.equal(rect.right + 200); expect(args.clientY).to.equal(rect.top); called = true; }); let rect = bar.contentNode.getBoundingClientRect(); simulate(document.body, 'mousemove', { clientX: rect.right + 200, clientY: rect.top }); expect(called).to.equal(true); }); it('should be handled by calling `releaseMouse` and removing the tab', () => { simulateOnNode(tab, 'mousedown'); let called = false; bar.tabDetachRequested.connect((sender, args) => { bar.releaseMouse(); bar.removeTabAt(args.index); called = true; }); let rect = bar.contentNode.getBoundingClientRect(); simulate(document.body, 'mousemove', { clientX: rect.right + 200, clientY: rect.top }); expect(called).to.equal(true); }); it('should only be emitted once per drag cycle', () => { simulateOnNode(tab, 'mousedown'); let called = 0; bar.tabDetachRequested.connect((sender, args) => { bar.releaseMouse(); bar.removeTabAt(args.index); called++; }); let rect = bar.contentNode.getBoundingClientRect(); simulate(document.body, 'mousemove', { clientX: rect.right + 200, clientY: rect.top }); expect(called).to.equal(1); simulate(document.body, 'mousemove', { clientX: rect.right + 201, clientY: rect.top }); expect(called).to.equal(1); }); it('should add the `lm-mod-dragging` class to the tab and the bar', () => { simulateOnNode(tab, 'mousedown'); let called = false; bar.tabDetachRequested.connect((sender, args) => { expect(tab.classList.contains('lm-mod-dragging')).to.equal(true); expect(bar.hasClass('lm-mod-dragging')).to.equal(true); called = true; }); let rect = bar.contentNode.getBoundingClientRect(); simulate(document.body, 'mousemove', { clientX: rect.right + 200, clientY: rect.top }); expect(called).to.equal(true); }); }); describe('#renderer', () => { it('should be the tab bar renderer', () => { let renderer = Object.create(TabBar.defaultRenderer); let bar = new TabBar({ renderer }); expect(bar.renderer).to.equal(renderer); }); it('should default to the default renderer', () => { let bar = new TabBar(); expect(bar.renderer).to.equal(TabBar.defaultRenderer); }); }); describe('#tabsMovable', () => { it('should get whether the tabs are movable by the user', () => { let bar = new TabBar(); expect(bar.tabsMovable).to.equal(false); }); it('should set whether the tabs are movable by the user', () => { let bar = new TabBar(); bar.tabsMovable = true; expect(bar.tabsMovable).to.equal(true); }); it('should still allow programmatic moves', () => { populateBar(bar); let titles = bar.titles.slice(); bar.insertTab(2, titles[0]); expect(bar.titles[2]).to.equal(titles[0]); }); }); describe('#addButtonEnabled', () => { it('should get whether the add button is enabled', () => { let bar = new TabBar(); expect(bar.addButtonEnabled).to.equal(false); }); it('should set whether the add button is enabled', () => { let bar = new TabBar(); bar.addButtonEnabled = true; expect(bar.addButtonEnabled).to.equal(true); }); it('should not show the add button if not set', () => { populateBar(bar); expect(bar.addButtonNode.classList.contains('lm-mod-hidden')).to.equal( true ); bar.addButtonEnabled = true; expect(bar.addButtonNode.classList.contains('lm-mod-hidden')).to.equal( false ); }); }); describe('#allowDeselect', () => { it('should determine whether a tab can be deselected by the user', () => { populateBar(bar); bar.allowDeselect = false; bar.tabsMovable = false; bar.currentIndex = 2; // Force the tabs to render MessageLoop.sendMessage(bar, Widget.Msg.UpdateRequest); let tab = bar.contentNode.getElementsByClassName( 'lm-TabBar-tab' )[2] as HTMLElement; simulateOnNode(tab, 'mousedown'); expect(bar.currentIndex).to.equal(2); simulateOnNode(tab, 'mouseup'); bar.allowDeselect = true; simulateOnNode(tab, 'mousedown'); expect(bar.currentIndex).to.equal(-1); simulateOnNode(tab, 'mouseup'); }); it('should always allow programmatic deselection', () => { populateBar(bar); bar.allowDeselect = false; bar.currentIndex = -1; expect(bar.currentIndex).to.equal(-1); }); }); describe('#insertBehavior', () => { it('should not change the selection', () => { populateBar(bar); bar.insertBehavior = 'none'; bar.currentIndex = 0; bar.insertTab(2, new Widget().title); expect(bar.currentIndex).to.equal(0); }); it('should select the tab', () => { populateBar(bar); bar.insertBehavior = 'select-tab'; bar.currentIndex = 0; bar.insertTab(2, new Widget().title); expect(bar.currentIndex).to.equal(2); bar.currentIndex = -1; bar.insertTab(1, new Widget().title); expect(bar.currentIndex).to.equal(1); }); it('should select the tab if needed', () => { populateBar(bar); bar.insertBehavior = 'select-tab-if-needed'; bar.currentIndex = 0; bar.insertTab(2, new Widget().title); expect(bar.currentIndex).to.equal(0); bar.currentIndex = -1; bar.insertTab(1, new Widget().title); expect(bar.currentIndex).to.equal(1); }); }); describe('#removeBehavior', () => { it('should select no tab', () => { populateBar(bar); bar.removeBehavior = 'none'; bar.currentIndex = 2; bar.removeTabAt(2); expect(bar.currentIndex).to.equal(-1); }); it('should select the tab after the removed tab if possible', () => { populateBar(bar); bar.removeBehavior = 'select-tab-after'; bar.currentIndex = 0; bar.removeTabAt(0); expect(bar.currentIndex).to.equal(0); bar.currentIndex = 1; bar.removeTabAt(1); expect(bar.currentIndex).to.equal(0); }); it('should select the tab before the removed tab if possible', () => { populateBar(bar); bar.removeBehavior = 'select-tab-before'; bar.currentIndex = 1; bar.removeTabAt(1); expect(bar.currentIndex).to.equal(0); bar.removeTabAt(0); expect(bar.currentIndex).to.equal(0); }); it('should select the previously selected tab if possible', () => { populateBar(bar); bar.removeBehavior = 'select-previous-tab'; bar.currentIndex = 0; bar.currentIndex = 2; bar.removeTabAt(2); expect(bar.currentIndex).to.equal(0); // Reset the bar. bar.removeTabAt(0); bar.removeTabAt(0); populateBar(bar); bar.currentIndex = 1; bar.removeTabAt(1); expect(bar.currentIndex).to.equal(0); }); }); describe('#currentTitle', () => { it('should get the currently selected title', () => { populateBar(bar); bar.currentIndex = 0; expect(bar.currentTitle).to.equal(bar.titles[0]); }); it('should be `null` if no tab is selected', () => { populateBar(bar); bar.currentIndex = -1; expect(bar.currentTitle).to.equal(null); }); it('should set the currently selected title', () => { populateBar(bar); bar.currentTitle = bar.titles[1]; expect(bar.currentTitle).to.equal(bar.titles[1]); }); it('should set the title to `null` if the title does not exist', () => { populateBar(bar); bar.currentTitle = new Widget().title; expect(bar.currentTitle).to.equal(null); }); }); describe('#currentIndex', () => { it('should get index of the currently selected tab', () => { populateBar(bar); expect(bar.currentIndex).to.equal(0); }); it('should be `null` if no tab is selected', () => { expect(bar.currentIndex).to.equal(-1); }); it('should set index of the currently selected tab', () => { populateBar(bar); bar.currentIndex = 1; expect(bar.currentIndex).to.equal(1); }); it('should set the index to `-1` if the value is out of range', () => { populateBar(bar); bar.currentIndex = -1; expect(bar.currentIndex).to.equal(-1); bar.currentIndex = 10; expect(bar.currentIndex).to.equal(-1); }); it('should emit the `currentChanged` signal', () => { populateBar(bar); let titles = bar.titles; let called = false; bar.currentChanged.connect((sender, args) => { expect(sender).to.equal(bar); expect(args.previousIndex).to.equal(0); expect(args.previousTitle).to.equal(titles[0]); expect(args.currentIndex).to.equal(1); expect(args.currentTitle).to.equal(titles[1]); called = true; }); bar.currentIndex = 1; expect(called).to.equal(true); }); it('should schedule an update of the tabs', done => { populateBar(bar); requestAnimationFrame(() => { bar.currentIndex = 1; bar.methods = []; requestAnimationFrame(() => { expect(bar.methods.indexOf('onUpdateRequest')).to.not.equal(-1); done(); }); }); }); it('should be a no-op if the index does not change', done => { populateBar(bar); requestAnimationFrame(() => { bar.currentIndex = 0; bar.methods = []; requestAnimationFrame(() => { expect(bar.methods.indexOf('onUpdateRequest')).to.equal(-1); done(); }); }); }); }); describe('#orientation', () => { it('should be the orientation of the tab bar', () => { expect(bar.orientation).to.equal('horizontal'); bar.orientation = 'vertical'; expect(bar.orientation).to.equal('vertical'); }); it('should set the orientation attribute of the tab bar', () => { bar.orientation = 'horizontal'; expect(bar.node.getAttribute('data-orientation')).to.equal( 'horizontal' ); bar.orientation = 'vertical'; expect(bar.node.getAttribute('data-orientation')).to.equal('vertical'); }); }); describe('#titles', () => { it('should get the read-only array of titles in the tab bar', () => { let bar = new TabBar(); let widgets = [new Widget(), new Widget(), new Widget()]; each(widgets, widget => { bar.addTab(widget.title); }); expect(bar.titles.length).to.equal(3); each(bar.titles, (title, i) => { expect(title.owner).to.equal(widgets[i]); }); }); }); describe('#contentNode', () => { it('should get the tab bar content node', () => { expect( bar.contentNode.classList.contains('lm-TabBar-content') ).to.equal(true); }); }); describe('#addTab()', () => { it('should add a tab to the end of the tab bar', () => { populateBar(bar); let title = new Widget().title; bar.addTab(title); expect(bar.titles[3]).to.equal(title); }); it('should accept a title options object', () => { let owner = new Widget(); bar.addTab({ owner, label: 'foo' }); expect(bar.titles[0]).to.be.an.instanceof(Title); expect(bar.titles[0].label).to.equal('foo'); }); it('should move an existing title to the end', () => { populateBar(bar); let titles = bar.titles.slice(); bar.addTab(titles[0]); expect(bar.titles[2]).to.equal(titles[0]); }); }); describe('#insertTab()', () => { it('should insert a tab into the tab bar at the specified index', () => { populateBar(bar); let title = new Widget().title; bar.insertTab(1, title); expect(bar.titles[1]).to.equal(title); }); it('should accept a title options object', () => { populateBar(bar); let title = bar.insertTab(1, { owner: new Widget(), label: 'foo' }); expect(title).to.be.an.instanceof(Title); expect(title.label).to.equal('foo'); }); it('should clamp the index to the bounds of the tabs', () => { populateBar(bar); let title = new Widget().title; bar.insertTab(-1, title); expect(bar.titles[0]).to.equal(title); title = new Widget().title; bar.insertTab(10, title); expect(bar.titles[4]).to.equal(title); }); it('should move an existing tab', () => { populateBar(bar); let titles = bar.titles.slice(); bar.insertTab(1, titles[0]); expect(bar.titles[1]).to.equal(titles[0]); }); it('should schedule an update of the tabs', done => { let bar = new LogTabBar(); bar.insertTab(0, new Widget().title); requestAnimationFrame(() => { expect(bar.methods.indexOf('onUpdateRequest')).to.not.equal(-1); done(); }); }); it('should schedule an update if the title changes', done => { let bar = new LogTabBar(); let title = new Widget().title; bar.insertTab(0, title); requestAnimationFrame(() => { expect(bar.methods.indexOf('onUpdateRequest')).to.not.equal(-1); bar.methods = []; title.label = 'foo'; requestAnimationFrame(() => { expect(bar.methods.indexOf('onUpdateRequest')).to.not.equal(-1); done(); }); }); }); }); describe('#removeTab()', () => { it('should remove a tab from the tab bar by value', () => { populateBar(bar); let titles = bar.titles.slice(); bar.removeTab(titles[0]); expect(bar.titles[0]).to.equal(titles[1]); }); it('should return be a no-op if the title is not in the tab bar', () => { populateBar(bar); bar.removeTab(new Widget().title); }); it('should schedule an update of the tabs', done => { let bar = new LogTabBar(); bar.insertTab(0, new Widget().title); requestAnimationFrame(() => { bar.removeTab(bar.titles[0]); bar.methods = []; requestAnimationFrame(() => { expect(bar.methods.indexOf('onUpdateRequest')).to.not.equal(-1); done(); }); }); }); }); describe('#removeTabAt()', () => { it('should remove a tab at a specific index', () => { populateBar(bar); let titles = bar.titles.slice(); bar.removeTabAt(0); expect(bar.titles[0]).to.equal(titles[1]); }); it('should return be a no-op if the index is out of range', () => { populateBar(bar); bar.removeTabAt(9); }); it('should schedule an update of the tabs', done => { let bar = new LogTabBar(); bar.insertTab(0, new Widget().title); requestAnimationFrame(() => { bar.removeTabAt(0); bar.methods = []; requestAnimationFrame(() => { expect(bar.methods.indexOf('onUpdateRequest')).to.not.equal(-1); done(); }); }); }); }); describe('#clearTabs()', () => { it('should remove all tabs from the tab bar', () => { populateBar(bar); bar.clearTabs(); expect(bar.titles.length).to.equal(0); }); it('should be a no-op if there are no tabs', () => { let bar = new TabBar(); bar.clearTabs(); expect(bar.titles.length).to.equal(0); }); it('should emit the `currentChanged` signal if there was a selected tab', () => { populateBar(bar); let called = false; bar.currentIndex = 0; bar.currentChanged.connect((sender, args) => { expect(sender).to.equal(bar); expect(args.previousIndex).to.equal(0); called = true; }); bar.clearTabs(); expect(called).to.equal(true); }); it('should not emit the `currentChanged` signal if there was no selected tab', () => { populateBar(bar); let called = false; bar.currentIndex = -1; bar.currentChanged.connect((sender, args) => { called = true; }); bar.clearTabs(); expect(called).to.equal(false); }); }); describe('#releaseMouse()', () => { it('should release the mouse and restore the non-dragged tab positions', () => { populateBar(bar); startDrag(bar, 0, 'left'); bar.releaseMouse(); simulate(document.body, 'mousemove'); expect(bar.events.indexOf('mousemove')).to.equal(-1); }); }); describe('#handleEvent()', () => { let tab: Element; let closeIcon: Element; beforeEach(() => { bar.tabsMovable = true; populateBar(bar); bar.currentIndex = 0; tab = bar.contentNode.children[0]; closeIcon = tab.querySelector(bar.renderer.closeIconSelector)!; }); context('left click', () => { it('should emit a tab close requested signal', () => { let called = false; let rect = closeIcon.getBoundingClientRect(); bar.tabCloseRequested.connect((sender, args) => { expect(sender).to.equal(bar); expect(args.index).to.equal(0); expect(args.title).to.equal(bar.titles[0]); called = true; }); simulate(closeIcon, 'mousedown', { clientX: rect.left, clientY: rect.top, button: 0 }); simulate(closeIcon, 'mouseup', { clientX: rect.left, clientY: rect.top, button: 0 }); expect(called).to.equal(true); }); it('should do nothing if a drag is in progress', () => { startDrag(bar, 1, 'up'); let called = false; let rect = closeIcon.getBoundingClientRect(); bar.tabCloseRequested.connect((sender, args) => { called = true; }); simulate(closeIcon, 'mousedown', { clientX: rect.left, clientY: rect.top, button: 0 }); simulate(closeIcon, 'mouseup', { clientX: rect.left, clientY: rect.top, button: 0 }); expect(called).to.equal(false); }); it('should do nothing if the click is not on a close icon', () => { let called = false; let rect = closeIcon.getBoundingClientRect(); bar.tabCloseRequested.connect((sender, args) => { called = true; }); simulate(closeIcon, 'mousedown', { clientX: rect.left, clientY: rect.top, button: 0 }); simulate(closeIcon, 'mouseup', { clientX: rect.left - 1, clientY: rect.top - 1, button: 0 }); expect(called).to.equal(false); expect(called).to.equal(false); }); it('should do nothing if the tab is not closable', () => { let called = false; bar.titles[0].closable = false; let rect = closeIcon.getBoundingClientRect(); bar.tabCloseRequested.connect((sender, args) => { called = true; }); simulate(closeIcon, 'mousedown', { clientX: rect.left, clientY: rect.top, button: 0 }); simulate(closeIcon, 'mouseup', { clientX: rect.left, clientY: rect.top, button: 0 }); expect(called).to.equal(false); }); }); context('middle click', () => { it('should emit a tab close requested signal', () => { let called = false; let rect = tab.getBoundingClientRect(); bar.tabCloseRequested.connect((sender, args) => { expect(sender).to.equal(bar); expect(args.index).to.equal(0); expect(args.title).to.equal(bar.titles[0]); called = true; }); simulate(tab, 'mousedown', { clientX: rect.left, clientY: rect.top, button: 1 }); simulate(tab, 'mouseup', { clientX: rect.left, clientY: rect.top, button: 1 }); expect(called).to.equal(true); }); it('should do nothing if a drag is in progress', () => { startDrag(bar, 1, 'up'); let called = false; let rect = tab.getBoundingClientRect(); bar.tabCloseRequested.connect((sender, args) => { called = true; }); simulate(tab, 'mousedown', { clientX: rect.left, clientY: rect.top, button: 1 }); simulate(tab, 'mouseup', { clientX: rect.left, clientY: rect.top, button: 1 }); expect(called).to.equal(false); }); it('should do nothing if the click is not on the tab', () => { let called = false; let rect = tab.getBoundingClientRect(); bar.tabCloseRequested.connect((sender, args) => { called = true; }); simulate(tab, 'mousedown', { clientX: rect.left, clientY: rect.top, button: 1 }); simulate(tab, 'mouseup', { clientX: rect.left - 1, clientY: rect.top - 1, button: 1 }); expect(called).to.equal(false); expect(called).to.equal(false); }); it('should do nothing if the tab is not closable', () => { let called = false; bar.titles[0].closable = false; let rect = tab.getBoundingClientRect(); bar.tabCloseRequested.connect((sender, args) => { called = true; }); simulate(tab, 'mousedown', { clientX: rect.left, clientY: rect.top, button: 1 }); simulate(tab, 'mouseup', { clientX: rect.left, clientY: rect.top, button: 1 }); expect(called).to.equal(false); }); }); context('mousedown', () => { it('should add event listeners if the tabs are movable', () => { simulateOnNode(tab, 'mousedown'); simulate(document.body, 'mousemove'); expect(bar.events.indexOf('mousemove')).to.not.equal(-1); }); it('should do nothing if not a left mouse press', () => { let rect = tab.getBoundingClientRect(); simulate(tab, 'mousedown', { clientX: rect.left, clientY: rect.top, button: 1 }); simulate(document.body, 'mousemove'); expect(bar.events.indexOf('mousemove')).to.equal(-1); }); it('should do nothing if the press is not on a tab', () => { let rect = tab.getBoundingClientRect(); simulate(tab, 'mousedown', { clientX: rect.left - 1, clientY: rect.top }); simulate(document.body, 'mousemove'); expect(bar.events.indexOf('mousemove')).to.equal(-1); }); it('should do nothing if the press is on a close icon', () => { simulateOnNode(closeIcon, 'mousedown'); simulate(document.body, 'mousemove'); expect(bar.events.indexOf('mousemove')).to.equal(-1); }); it('should do nothing if the tabs are not movable', () => { bar.tabsMovable = false; simulateOnNode(tab, 'mousedown'); simulate(document.body, 'mousemove'); expect(bar.events.indexOf('mousemove')).to.equal(-1); }); it('should do nothing if there is a drag in progress', () => { startDrag(bar, 2, 'down'); let rect = tab.getBoundingClientRect(); let evt = generate('mousedown', { clientX: rect.left, clientY: rect.top }); let cancelled = !tab.dispatchEvent(evt); expect(cancelled).to.equal(false); }); }); context('mousemove', () => { it('should do nothing if there is a drag in progress', () => { simulateOnNode(tab, 'mousedown'); let called = 0; bar.tabDetachRequested.connect((sender, args) => { called++; }); let rect = bar.contentNode.getBoundingClientRect(); simulate(document.body, 'mousemove', { clientX: rect.right + 200, clientY: rect.top }); expect(called).to.equal(1); simulate(document.body, 'mousemove', { clientX: rect.right + 200, clientY: rect.top }); expect(called).to.equal(1); }); it('should bail if the drag threshold is not exceeded', () => { simulateOnNode(tab, 'mousedown'); let called = false; bar.tabDetachRequested.connect((sender, args) => { bar.releaseMouse(); called = true; }); let rect = bar.contentNode.getBoundingClientRect(); simulate(document.body, 'mousemove', { clientX: rect.right + 1, clientY: rect.top }); expect(called).to.equal(false); }); it('should emit the detach requested signal if the threshold is exceeded', () => { simulateOnNode(tab, 'mousedown'); let called = false; bar.tabDetachRequested.connect((sender, args) => { expect(sender).to.equal(bar); expect(args.index).to.equal(0); expect(args.title).to.equal(bar.titles[0]); expect(args.clientX).to.equal(rect.right + 200); expect(args.clientY).to.equal(rect.top); called = true; }); let rect = bar.contentNode.getBoundingClientRect(); simulate(document.body, 'mousemove', { clientX: rect.right + 200, clientY: rect.top }); expect(called).to.equal(true); }); it('should bail if the signal handler aborted the drag', () => { simulateOnNode(tab, 'mousedown'); let called = false; bar.tabDetachRequested.connect((sender, args) => { bar.releaseMouse(); called = true; }); let rect = bar.contentNode.getBoundingClientRect(); simulate(document.body, 'mousemove', { clientX: rect.right + 200, clientY: rect.top }); expect(called).to.equal(true); let left = rect.left; rect = tab.getBoundingClientRect(); expect(left).to.equal(rect.left); }); it('should update the positions of the tabs', () => { simulateOnNode(tab, 'mousedown'); let called = false; bar.tabDetachRequested.connect((sender, args) => { called = true; }); let rect = bar.contentNode.getBoundingClientRect(); simulate(document.body, 'mousemove', { clientX: rect.right + 200, clientY: rect.top }); expect(called).to.equal(true); let left = rect.left; rect = tab.getBoundingClientRect(); expect(left).to.not.equal(rect.left); }); }); context('mouseup', () => { it('should emit the `tabMoved` signal', done => { startDrag(bar); simulate(document.body, 'mouseup'); bar.tabMoved.connect(() => { done(); }); }); it('should move the tab to its final position', done => { startDrag(bar); simulate(document.body, 'mouseup'); let title = bar.titles[0]; bar.tabMoved.connect(() => { expect(bar.titles[2]).to.equal(title); done(); }); }); it('should cancel a middle mouse release', () => { startDrag(bar); let evt = generate('mouseup', { button: 1 }); let cancelled = !document.body.dispatchEvent(evt); expect(cancelled).to.equal(true); }); }); context('keydown', () => { it('should prevent default', () => { startDrag(bar); let evt = generate('keydown'); let cancelled = !document.body.dispatchEvent(evt); expect(cancelled).to.equal(true); }); it('should release the mouse if `Escape` is pressed', () => { startDrag(bar); simulate(document.body, 'keydown', { keyCode: 27 }); simulateOnNode(tab, 'mousedown'); expect(bar.events.indexOf('mousemove')).to.equal(-1); }); }); context('contextmenu', () => { it('should prevent default', () => { startDrag(bar); let evt = generate('contextmenu'); let cancelled = !document.body.dispatchEvent(evt); expect(cancelled).to.equal(true); }); }); }); describe('#onBeforeAttach()', () => { it('should add event listeners to the node', () => { let bar = new LogTabBar(); Widget.attach(bar, document.body); expect(bar.methods).to.contain('onBeforeAttach'); simulate(bar.node, 'mousedown'); expect(bar.events.indexOf('mousedown')).to.not.equal(-1); bar.dispose(); }); }); describe('#onAfterDetach()', () => { it('should remove event listeners', () => { let bar = new LogTabBar(); let owner = new Widget(); bar.addTab(new Title({ owner, label: 'foo' })); MessageLoop.sendMessage(bar, Widget.Msg.UpdateRequest); Widget.attach(bar, document.body); let tab = bar.contentNode.firstChild as HTMLElement; let rect = tab.getBoundingClientRect(); simulate(tab, 'mousedown', { clientX: rect.left, clientY: rect.top }); Widget.detach(bar); expect(bar.methods).to.contain('onAfterDetach'); simulate(document.body, 'mousemove'); expect(bar.events.indexOf('mousemove')).to.equal(-1); simulate(document.body, 'mouseup'); expect(bar.events.indexOf('mouseup')).to.equal(-1); }); }); describe('#onUpdateRequest()', () => { it('should render tabs and set styles', () => { populateBar(bar); bar.currentIndex = 0; MessageLoop.sendMessage(bar, Widget.Msg.UpdateRequest); expect(bar.methods.indexOf('onUpdateRequest')).to.not.equal(-1); each(bar.titles, (title, i) => { let tab = bar.contentNode.children[i] as HTMLElement; let label = tab.getElementsByClassName( 'lm-TabBar-tabLabel' )[0] as HTMLElement; expect(label.textContent).to.equal(title.label); let current = i === 0; expect(tab.classList.contains('lm-mod-current')).to.equal(current); }); }); }); describe('.Renderer', () => { let title: Title; beforeEach(() => { let owner = new Widget(); title = new Title({ owner, label: 'foo', closable: true, icon: 'bar', className: 'fizz', caption: 'this is a caption' }); }); describe('#closeIconSelector', () => { it('should be `.lm-TabBar-tabCloseIcon`', () => { let renderer = new TabBar.Renderer(); expect(renderer.closeIconSelector).to.equal( '.lm-TabBar-tabCloseIcon' ); }); }); describe('#renderTab()', () => { it('should render a virtual node for a tab', () => { let renderer = new TabBar.Renderer(); let vNode = renderer.renderTab({ title, current: true, zIndex: 1 }); let node = VirtualDOM.realize(vNode); expect( node.getElementsByClassName('lm-TabBar-tabIcon').length ).to.equal(1); expect( node.getElementsByClassName('lm-TabBar-tabLabel').length ).to.equal(1); expect( node.getElementsByClassName('lm-TabBar-tabCloseIcon').length ).to.equal(1); expect(node.classList.contains('lm-TabBar-tab')).to.equal(true); expect(node.classList.contains(title.className)).to.equal(true); expect(node.classList.contains('lm-mod-current')).to.equal(true); expect(node.classList.contains('lm-mod-closable')).to.equal(true); expect(node.title).to.equal(title.caption); let label = node.getElementsByClassName( 'lm-TabBar-tabLabel' )[0] as HTMLElement; expect(label.textContent).to.equal(title.label); let icon = node.getElementsByClassName( 'lm-TabBar-tabIcon' )[0] as HTMLElement; expect(icon.classList.contains(title.iconClass)).to.equal(true); /* */ // since a string was assigned to .icon, it should alias .iconClass expect(icon.classList.contains(title.icon as string)).to.equal(true); expect(title.icon).to.equal(title.iconClass); /* */ }); }); describe('#renderIcon()', () => { it('should render the icon element for a tab', () => { let renderer = new TabBar.Renderer(); let vNode = renderer.renderIcon({ title, current: true, zIndex: 1 }); let node = VirtualDOM.realize(vNode as VirtualElement); expect(node.className).to.contain('lm-TabBar-tabIcon'); expect(node.classList.contains(title.iconClass)).to.equal(true); /* */ // make sure that icon and iconClass match expect(node.classList.contains(title.icon as string)).to.equal(true); expect(title.icon).to.equal(title.iconClass); /* */ }); }); describe('#renderLabel()', () => { it('should render the label element for a tab', () => { let renderer = new TabBar.Renderer(); let vNode = renderer.renderLabel({ title, current: true, zIndex: 1 }); let label = VirtualDOM.realize(vNode); expect(label.className).to.contain('lm-TabBar-tabLabel'); expect(label.textContent).to.equal(title.label); }); }); describe('#renderCloseIcon()', () => { it('should render the close icon element for a tab', () => { let renderer = new TabBar.Renderer(); let vNode = renderer.renderCloseIcon({ title, current: true, zIndex: 1 }); let icon = VirtualDOM.realize(vNode); expect(icon.className).to.contain('lm-TabBar-tabCloseIcon'); }); }); describe('#createTabKey()', () => { it('should create a unique render key for the tab', () => { let renderer = new TabBar.Renderer(); let key = renderer.createTabKey({ title, current: true, zIndex: 1 }); let newKey = renderer.createTabKey({ title, current: true, zIndex: 1 }); expect(key).to.equal(newKey); }); }); describe('#createTabStyle()', () => { it('should create the inline style object for a tab', () => { let renderer = new TabBar.Renderer(); let style = renderer.createTabStyle({ title, current: true, zIndex: 1 }); expect(style['zIndex']).to.equal('1'); }); }); describe('#createTabClass()', () => { it('should create the class name for the tab', () => { let renderer = new TabBar.Renderer(); let className = renderer.createTabClass({ title, current: true, zIndex: 1 }); expect(className).to.contain('lm-TabBar-tab'); expect(className).to.contain('lm-mod-closable'); expect(className).to.contain('lm-mod-current'); }); }); describe('#createIconClass()', () => { it('should create class name for the tab icon', () => { let renderer = new TabBar.Renderer(); let className = renderer.createIconClass({ title, current: true, zIndex: 1 }); expect(className).to.contain('lm-TabBar-tabIcon'); expect(className).to.contain(title.iconClass); /* */ // make sure that icon and iconClass match expect(className).to.contain(title.icon as string); expect(title.icon).to.equal(title.iconClass); /* */ }); }); }); describe('.defaultRenderer', () => { it('should be an instance of `Renderer`', () => { expect(TabBar.defaultRenderer).to.be.an.instanceof(TabBar.Renderer); }); }); }); }); lumino-2021.12.13/packages/widgets/tests/src/tabpanel.spec.ts000066400000000000000000000304551415564225700236740ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { expect } from 'chai'; import { each } from '@lumino/algorithm'; import { StackedPanel, TabBar, TabPanel, Widget } from '@lumino/widgets'; import { LogWidget } from './widget.spec'; describe('@lumino/widgets', () => { describe('TabPanel', () => { describe('#constructor()', () => { it('should construct a new tab panel and take no arguments', () => { let panel = new TabPanel(); expect(panel).to.be.an.instanceof(TabPanel); }); it('should accept options', () => { let renderer = Object.create(TabBar.defaultRenderer); let panel = new TabPanel({ tabPlacement: 'left', tabsMovable: true, renderer }); expect(panel.tabBar.tabsMovable).to.equal(true); expect(panel.tabBar.renderer).to.equal(renderer); }); it('should add a `lm-TabPanel` class', () => { let panel = new TabPanel(); expect(panel.hasClass('lm-TabPanel')).to.equal(true); }); }); describe('#dispose()', () => { it('should dispose of the resources held by the widget', () => { let panel = new TabPanel(); panel.addWidget(new Widget()); panel.dispose(); expect(panel.isDisposed).to.equal(true); panel.dispose(); expect(panel.isDisposed).to.equal(true); }); }); describe('#currentChanged', () => { it('should be emitted when the current tab is changed', () => { let panel = new TabPanel(); panel.addWidget(new Widget()); panel.addWidget(new Widget()); let called = false; let widgets = panel.widgets; panel.currentChanged.connect((sender, args) => { expect(sender).to.equal(panel); expect(args.previousIndex).to.equal(0); expect(args.previousWidget).to.equal(widgets[0]); expect(args.currentIndex).to.equal(1); expect(args.currentWidget).to.equal(widgets[1]); called = true; }); panel.currentIndex = 1; expect(called).to.equal(true); }); it('should not be emitted when another tab is inserted', () => { let panel = new TabPanel(); panel.addWidget(new Widget()); panel.addWidget(new Widget()); let called = false; panel.currentChanged.connect((sender, args) => { called = true; }); panel.insertWidget(0, new Widget()); expect(called).to.equal(false); }); it('should not be emitted when another tab is removed', () => { let panel = new TabPanel(); panel.addWidget(new Widget()); panel.addWidget(new Widget()); let called = false; panel.currentIndex = 1; panel.currentChanged.connect((sender, args) => { called = true; }); panel.widgets[0].parent = null; expect(called).to.equal(false); }); it('should not be emitted when the current tab is moved', () => { let panel = new TabPanel(); panel.addWidget(new Widget()); panel.addWidget(new Widget()); let called = false; panel.currentChanged.connect((sender, args) => { called = true; }); panel.insertWidget(2, panel.widgets[0]); expect(called).to.equal(false); }); }); describe('#currentIndex', () => { it('should get the index of the currently selected tab', () => { let panel = new TabPanel(); panel.addWidget(new Widget()); expect(panel.currentIndex).to.equal(0); }); it('should be `-1` if no tab is selected', () => { let panel = new TabPanel(); expect(panel.currentIndex).to.equal(-1); }); it('should set the index of the currently selected tab', () => { let panel = new TabPanel(); panel.addWidget(new Widget()); panel.addWidget(new Widget()); panel.currentIndex = 1; expect(panel.currentIndex).to.equal(1); }); it('should set the index to `-1` if out of range', () => { let panel = new TabPanel(); panel.addWidget(new Widget()); panel.addWidget(new Widget()); panel.currentIndex = -2; expect(panel.currentIndex).to.equal(-1); panel.currentIndex = 2; expect(panel.currentIndex).to.equal(-1); }); }); describe('#currentWidget', () => { it('should get the currently selected tab', () => { let panel = new TabPanel(); let widget = new Widget(); panel.addWidget(widget); expect(panel.currentWidget).to.equal(widget); }); it('should be `null` if no tab is selected', () => { let panel = new TabPanel(); expect(panel.currentWidget).to.equal(null); }); it('should set the currently selected tab', () => { let panel = new TabPanel(); panel.addWidget(new Widget()); let widget = new Widget(); panel.addWidget(widget); panel.currentWidget = widget; expect(panel.currentWidget).to.equal(widget); }); it('should set `null` if the widget is not in the panel', () => { let panel = new TabPanel(); panel.addWidget(new Widget()); panel.addWidget(new Widget()); panel.currentWidget = new Widget(); expect(panel.currentWidget).to.equal(null); }); }); describe('#tabsMovable', () => { it('should be the tabsMovable property of the tabBar', () => { let panel = new TabPanel(); expect(panel.tabsMovable).to.equal(false); panel.tabsMovable = true; expect(panel.tabBar.tabsMovable).to.equal(true); }); }); describe('#tabPlacement', () => { it('should be the tab placement for the tab panel', () => { let panel = new TabPanel(); expect(panel.tabPlacement).to.equal('top'); expect(panel.tabBar.orientation).to.equal('horizontal'); expect(panel.tabBar.node.getAttribute('data-placement')).to.equal( 'top' ); panel.tabPlacement = 'bottom'; expect(panel.tabBar.orientation).to.equal('horizontal'); expect(panel.tabBar.node.getAttribute('data-placement')).to.equal( 'bottom' ); panel.tabPlacement = 'left'; expect(panel.tabBar.orientation).to.equal('vertical'); expect(panel.tabBar.node.getAttribute('data-placement')).to.equal( 'left' ); panel.tabPlacement = 'right'; expect(panel.tabBar.orientation).to.equal('vertical'); expect(panel.tabBar.node.getAttribute('data-placement')).to.equal( 'right' ); }); }); describe('#tabBar', () => { it('should get the tab bar associated with the tab panel', () => { let panel = new TabPanel(); let bar = panel.tabBar; expect(bar).to.be.an.instanceof(TabBar); }); it('should have the "lm-TabPanel-tabBar" class', () => { let panel = new TabPanel(); let bar = panel.tabBar; expect(bar.hasClass('lm-TabPanel-tabBar')).to.equal(true); }); it('should move the widget in the stacked panel when a tab is moved', () => { let panel = new TabPanel(); let widgets = [new LogWidget(), new LogWidget()]; each(widgets, w => { panel.addWidget(w); }); Widget.attach(panel, document.body); let bar = panel.tabBar; let called = false; bar.tabMoved.connect(() => { let stack = panel.stackedPanel; expect(stack.widgets[1]).to.equal(widgets[0]); called = true; }); (bar.tabMoved as any).emit({ fromIndex: 0, toIndex: 1, title: widgets[0].title }); expect(called).to.equal(true); panel.dispose(); }); it('should show the new widget when the current tab changes', () => { let panel = new TabPanel(); let widgets = [new LogWidget(), new LogWidget(), new LogWidget()]; each(widgets, w => { panel.addWidget(w); }); each(widgets, w => { w.node.tabIndex = -1; }); Widget.attach(panel, document.body); panel.tabBar.currentChanged.connect((sender, args) => { expect(widgets[args.previousIndex].isVisible).to.equal(false); expect(widgets[args.currentIndex].isVisible).to.equal(true); }); panel.tabBar.currentIndex = 1; panel.dispose(); }); it('should close the widget when a tab is closed', () => { let panel = new TabPanel(); let widget = new LogWidget(); panel.addWidget(widget); Widget.attach(panel, document.body); let bar = panel.tabBar; let called = false; bar.tabCloseRequested.connect(() => { expect(widget.methods.indexOf('onCloseRequest')).to.not.equal(-1); called = true; }); (bar.tabCloseRequested as any).emit({ index: 0, title: widget.title }); expect(called).to.equal(true); panel.dispose(); }); }); describe('#stackedPanel', () => { it('should get the stacked panel associated with the tab panel', () => { let panel = new TabPanel(); let stack = panel.stackedPanel; expect(stack).to.be.an.instanceof(StackedPanel); }); it('should have the "lm-TabPanel-stackedPanel" class', () => { let panel = new TabPanel(); let stack = panel.stackedPanel; expect(stack.hasClass('lm-TabPanel-stackedPanel')).to.equal(true); }); it('remove a tab when a widget is removed from the stacked panel', () => { let panel = new TabPanel(); let widget = new Widget(); panel.addWidget(widget); let stack = panel.stackedPanel; let called = false; stack.widgetRemoved.connect(() => { let bar = panel.tabBar; expect(bar.titles).to.deep.equal([]); called = true; }); widget.parent = null; expect(called).to.equal(true); }); }); describe('#widgets', () => { it('should get a read-only array of the widgets in the panel', () => { let panel = new TabPanel(); let widgets = [new Widget(), new Widget(), new Widget()]; each(widgets, w => { panel.addWidget(w); }); expect(panel.widgets).to.deep.equal(widgets); }); }); describe('#addWidget()', () => { it('should add a widget to the end of the tab panel', () => { let panel = new TabPanel(); let widgets = [new Widget(), new Widget(), new Widget()]; each(widgets, w => { panel.addWidget(w); }); let widget = new Widget(); panel.addWidget(widget); expect(panel.widgets[3]).to.equal(widget); expect(panel.tabBar.titles[2]).to.equal(widgets[2].title); }); it('should move an existing widget', () => { let panel = new TabPanel(); let widgets = [new Widget(), new Widget(), new Widget()]; each(widgets, w => { panel.addWidget(w); }); panel.addWidget(widgets[0]); expect(panel.widgets[2]).to.equal(widgets[0]); }); }); describe('#insertWidget()', () => { it('should insert a widget into the tab panel at a specified index', () => { let panel = new TabPanel(); let widgets = [new Widget(), new Widget(), new Widget()]; each(widgets, w => { panel.addWidget(w); }); let widget = new Widget(); panel.insertWidget(1, widget); expect(panel.widgets[1]).to.equal(widget); expect(panel.tabBar.titles[1]).to.equal(widget.title); }); it('should move an existing widget', () => { let panel = new TabPanel(); let widgets = [new Widget(), new Widget(), new Widget()]; each(widgets, w => { panel.addWidget(w); }); panel.insertWidget(0, widgets[2]); expect(panel.widgets[0]).to.equal(widgets[2]); }); }); }); }); lumino-2021.12.13/packages/widgets/tests/src/title.spec.ts000066400000000000000000000270241415564225700232250ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { expect } from 'chai'; import { Title } from '@lumino/widgets'; const owner = { name: 'Bob' }; describe('@lumino/widgets', () => { describe('Title', () => { describe('#constructor()', () => { it('should accept title options', () => { let title = new Title({ owner }); expect(title).to.be.an.instanceof(Title); }); }); describe('#changed', () => { it('should be emitted when the title state changes', () => { let called = false; let title = new Title({ owner }); title.changed.connect((sender, arg) => { expect(sender).to.equal(title); expect(arg).to.equal(undefined); called = true; }); title.label = 'baz'; expect(called).to.equal(true); }); }); describe('#owner', () => { it('should be the title owner', () => { let title = new Title({ owner }); expect(title.owner).to.equal(owner); }); }); describe('#label', () => { it('should default to an empty string', () => { let title = new Title({ owner }); expect(title.label).to.equal(''); }); it('should initialize from the options', () => { let title = new Title({ owner, label: 'foo' }); expect(title.label).to.equal('foo'); }); it('should be writable', () => { let title = new Title({ owner, label: 'foo' }); title.label = 'bar'; expect(title.label).to.equal('bar'); }); it('should emit the changed signal when the value changes', () => { let called = false; let title = new Title({ owner, label: 'foo' }); title.changed.connect((sender, arg) => { expect(sender).to.equal(title); expect(arg).to.equal(undefined); called = true; }); title.label = 'baz'; expect(called).to.equal(true); }); it('should not emit the changed signal when the value does not change', () => { let called = false; let title = new Title({ owner, label: 'foo' }); title.changed.connect((sender, arg) => { called = true; }); title.label = 'foo'; expect(called).to.equal(false); }); }); describe('#mnemonic', () => { it('should default to `-1', () => { let title = new Title({ owner }); expect(title.mnemonic).to.equal(-1); }); it('should initialize from the options', () => { let title = new Title({ owner, mnemonic: 1 }); expect(title.mnemonic).to.equal(1); }); it('should be writable', () => { let title = new Title({ owner, mnemonic: 1 }); title.mnemonic = 2; expect(title.mnemonic).to.equal(2); }); it('should emit the changed signal when the value changes', () => { let called = false; let title = new Title({ owner, mnemonic: 1 }); title.changed.connect((sender, arg) => { expect(sender).to.equal(title); expect(arg).to.equal(undefined); called = true; }); title.mnemonic = 0; expect(called).to.equal(true); }); it('should not emit the changed signal when the value does not change', () => { let called = false; let title = new Title({ owner, mnemonic: 1 }); title.changed.connect((sender, arg) => { called = true; }); title.mnemonic = 1; expect(called).to.equal(false); }); }); describe('#icon', () => { const iconRenderer = { render: (host: HTMLElement, options?: any) => { const renderNode = document.createElement('div'); renderNode.className = 'p-render'; host.appendChild(renderNode); } }; it('should default to an empty string', () => { let title = new Title({ owner }); expect(title.icon).to.equal(''); }); it('should initialize from the options', () => { let title = new Title({ owner, icon: 'foo' }); expect(title.icon).to.equal('foo'); }); it('should be writable', () => { let title = new Title({ owner, icon: 'foo' }); title.icon = 'bar'; expect(title.icon).to.equal('bar'); }); it('should emit the changed signal when the value changes', () => { let called = false; let title = new Title({ owner, icon: 'foo' }); title.changed.connect((sender, arg) => { expect(sender).to.equal(title); expect(arg).to.equal(undefined); called = true; }); title.icon = 'baz'; expect(called).to.equal(true); }); it('should not emit the changed signal when the value does not change', () => { let called = false; let title = new Title({ owner, icon: 'foo' }); title.changed.connect((sender, arg) => { called = true; }); title.icon = 'foo'; expect(called).to.equal(false); }); /* */ it('should be able to switch string => renderer', () => { let title = new Title({ owner, icon: 'foo' }); expect(title.icon).to.equal('foo'); // when initialized with string, should alias .iconClass expect(title.icon).to.equal(title.iconClass); title.icon = iconRenderer; expect(title.icon).to.equal(iconRenderer); }); it('should be able to switch renderer => string', () => { let title = new Title({ owner, icon: iconRenderer }); expect(title.icon).to.equal(iconRenderer); title.icon = 'foo'; expect(title.icon).to.equal('foo'); // when switched to string, should alias .iconClass expect(title.icon).to.equal(title.iconClass); }); it('should alias .iconClass if unset', () => { let title = new Title({ owner, iconClass: 'foo' }); expect(title.icon).to.equal('foo'); }); /* */ }); describe('#caption', () => { it('should default to an empty string', () => { let title = new Title({ owner }); expect(title.caption).to.equal(''); }); it('should initialize from the options', () => { let title = new Title({ owner, caption: 'foo' }); expect(title.caption).to.equal('foo'); }); it('should be writable', () => { let title = new Title({ owner, caption: 'foo' }); title.caption = 'bar'; expect(title.caption).to.equal('bar'); }); it('should emit the changed signal when the value changes', () => { let called = false; let title = new Title({ owner, caption: 'foo' }); title.changed.connect((sender, arg) => { expect(sender).to.equal(title); expect(arg).to.equal(undefined); called = true; }); title.caption = 'baz'; expect(called).to.equal(true); }); it('should not emit the changed signal when the value does not change', () => { let called = false; let title = new Title({ owner, caption: 'foo' }); title.changed.connect((sender, arg) => { called = true; }); title.caption = 'foo'; expect(called).to.equal(false); }); }); describe('#className', () => { it('should default to an empty string', () => { let title = new Title({ owner }); expect(title.className).to.equal(''); }); it('should initialize from the options', () => { let title = new Title({ owner, className: 'foo' }); expect(title.className).to.equal('foo'); }); it('should be writable', () => { let title = new Title({ owner, className: 'foo' }); title.className = 'bar'; expect(title.className).to.equal('bar'); }); it('should emit the changed signal when the value changes', () => { let called = false; let title = new Title({ owner, className: 'foo' }); title.changed.connect((sender, arg) => { expect(sender).to.equal(title); expect(arg).to.equal(undefined); called = true; }); title.className = 'baz'; expect(called).to.equal(true); }); it('should not emit the changed signal when the value does not change', () => { let called = false; let title = new Title({ owner, className: 'foo' }); title.changed.connect((sender, arg) => { called = true; }); title.className = 'foo'; expect(called).to.equal(false); }); }); describe('#closable', () => { it('should default to `false`', () => { let title = new Title({ owner }); expect(title.closable).to.equal(false); }); it('should initialize from the options', () => { let title = new Title({ owner, closable: true }); expect(title.closable).to.equal(true); }); it('should be writable', () => { let title = new Title({ owner, closable: true }); title.closable = false; expect(title.closable).to.equal(false); }); it('should emit the changed signal when the value changes', () => { let called = false; let title = new Title({ owner, closable: false }); title.changed.connect((sender, arg) => { expect(sender).to.equal(title); expect(arg).to.equal(undefined); called = true; }); title.closable = true; expect(called).to.equal(true); }); it('should not emit the changed signal when the value does not change', () => { let called = false; let title = new Title({ owner, closable: false }); title.changed.connect((sender, arg) => { called = true; }); title.closable = false; expect(called).to.equal(false); }); }); describe('#dataset', () => { it('should default to `{}`', () => { let title = new Title({ owner }); expect(title.dataset).to.deep.equal({}); }); it('should initialize from the options', () => { let title = new Title({ owner, dataset: { foo: '12' } }); expect(title.dataset).to.deep.equal({ foo: '12' }); }); it('should be writable', () => { let title = new Title({ owner, dataset: { foo: '12' } }); title.dataset = { bar: '42' }; expect(title.dataset).to.deep.equal({ bar: '42' }); }); it('should emit the changed signal when the value changes', () => { let called = false; let title = new Title({ owner, dataset: { foo: '12' } }); title.changed.connect((sender, arg) => { expect(sender).to.equal(title); expect(arg).to.equal(undefined); called = true; }); title.dataset = { bar: '42' }; expect(called).to.equal(true); }); it('should not emit the changed signal when the value does not change', () => { let called = false; let dataset = { foo: '12' }; let title = new Title({ owner, dataset }); title.changed.connect((sender, arg) => { called = true; }); title.dataset = dataset; expect(called).to.equal(false); }); }); }); }); lumino-2021.12.13/packages/widgets/tests/src/widget.spec.ts000066400000000000000000001167741415564225700234020ustar00rootroot00000000000000// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ import { expect } from 'chai'; import { ArrayExt, each, IIterator, iter } from '@lumino/algorithm'; import { Message, MessageLoop } from '@lumino/messaging'; import { Layout, Title, Widget } from '@lumino/widgets'; export class LogWidget extends Widget { messages: string[] = []; methods: string[] = []; raw: Message[] = []; processMessage(msg: Message): void { super.processMessage(msg); this.messages.push(msg.type); } protected notifyLayout(msg: Message): void { super.notifyLayout(msg); this.methods.push('notifyLayout'); } protected onActivateRequest(msg: Message): void { super.onActivateRequest(msg); this.methods.push('onActivateRequest'); } protected onCloseRequest(msg: Message): void { super.onCloseRequest(msg); this.methods.push('onCloseRequest'); } protected onResize(msg: Widget.ResizeMessage): void { super.onResize(msg); this.methods.push('onResize'); } protected onUpdateRequest(msg: Message): void { super.onUpdateRequest(msg); this.methods.push('onUpdateRequest'); } protected onAfterShow(msg: Message): void { super.onAfterShow(msg); this.methods.push('onAfterShow'); } protected onBeforeHide(msg: Message): void { super.onBeforeHide(msg); this.methods.push('onBeforeHide'); } protected onAfterAttach(msg: Message): void { super.onAfterAttach(msg); this.methods.push('onAfterAttach'); } protected onBeforeDetach(msg: Message): void { super.onBeforeDetach(msg); this.methods.push('onBeforeDetach'); } protected onChildAdded(msg: Widget.ChildMessage): void { super.onChildAdded(msg); this.methods.push('onChildAdded'); this.raw.push(msg); } protected onChildRemoved(msg: Widget.ChildMessage): void { super.onChildRemoved(msg); this.methods.push('onChildRemoved'); this.raw.push(msg); } } class TestLayout extends Layout { dispose(): void { while (this._widgets.length !== 0) { this._widgets.pop()!.dispose(); } super.dispose(); } iter(): IIterator { return iter(this._widgets); } removeWidget(widget: Widget): void { ArrayExt.removeFirstOf(this._widgets, widget); } private _widgets = [new Widget(), new Widget()]; } describe('@lumino/widgets', () => { describe('Widget', () => { describe('#constructor()', () => { it('should accept no arguments', () => { let widget = new Widget(); expect(widget).to.be.an.instanceof(Widget); }); it('should accept options', () => { let span = document.createElement('span'); let widget = new Widget({ node: span }); expect(widget.node).to.equal(span); }); it('should add the `lm-Widget` class', () => { let widget = new Widget(); expect(widget.hasClass('lm-Widget')).to.equal(true); }); }); describe('#dispose()', () => { it('should dispose of the widget', () => { let widget = new Widget(); widget.dispose(); expect(widget.isDisposed).to.equal(true); }); it('should be a no-op if the widget already disposed', () => { let called = false; let widget = new Widget(); widget.dispose(); widget.disposed.connect(() => { called = true; }); widget.dispose(); expect(called).to.equal(false); expect(widget.isDisposed).to.equal(true); }); it('should remove the widget from its parent', () => { let parent = new Widget(); let child = new Widget(); child.parent = parent; child.dispose(); expect(parent.isDisposed).to.equal(false); expect(child.isDisposed).to.equal(true); expect(child.parent).to.equal(null); }); it('should automatically detach the widget', () => { let widget = new Widget(); Widget.attach(widget, document.body); expect(widget.isAttached).to.equal(true); widget.dispose(); expect(widget.isAttached).to.equal(false); }); it('should dispose of the widget layout', () => { let widget = new Widget(); let layout = new TestLayout(); widget.layout = layout; widget.dispose(); expect(layout.isDisposed).to.equal(true); }); }); describe('#disposed', () => { it('should be emitted when the widget is disposed', () => { let called = false; let widget = new Widget(); widget.disposed.connect(() => { called = true; }); widget.dispose(); expect(called).to.equal(true); }); }); describe('#isDisposed', () => { it('should be `true` if the widget is disposed', () => { let widget = new Widget(); widget.dispose(); expect(widget.isDisposed).to.equal(true); }); it('should be `false` if the widget is not disposed', () => { let widget = new Widget(); expect(widget.isDisposed).to.equal(false); }); }); describe('#isAttached', () => { it('should be `true` if the widget is attached', () => { let widget = new Widget(); Widget.attach(widget, document.body); expect(widget.isAttached).to.equal(true); widget.dispose(); }); it('should be `false` if the widget is not attached', () => { let widget = new Widget(); expect(widget.isAttached).to.equal(false); }); }); describe('#isHidden', () => { it('should be `true` if the widget is hidden', () => { let widget = new Widget(); Widget.attach(widget, document.body); widget.hide(); expect(widget.isHidden).to.equal(true); widget.dispose(); }); it('should be `false` if the widget is not hidden', () => { let widget = new Widget(); Widget.attach(widget, document.body); expect(widget.isHidden).to.equal(false); widget.dispose(); }); }); describe('#isVisible', () => { it('should be `true` if the widget is visible', () => { let widget = new Widget(); Widget.attach(widget, document.body); expect(widget.isVisible).to.equal(true); widget.dispose(); }); it('should be `false` if the widget is not visible', () => { let widget = new Widget(); Widget.attach(widget, document.body); widget.hide(); expect(widget.isVisible).to.equal(false); widget.dispose(); }); it('should be `false` if the widget is not attached', () => { let widget = new Widget(); expect(widget.isVisible).to.equal(false); }); }); describe('#node', () => { it('should get the DOM node owned by the widget', () => { let widget = new Widget(); let node = widget.node; expect(node.tagName.toLowerCase()).to.equal('div'); }); }); describe('#id', () => { it('should get the id of the widget node', () => { let widget = new Widget(); widget.node.id = 'foo'; expect(widget.id).to.equal('foo'); }); it('should set the id of the widget node', () => { let widget = new Widget(); widget.id = 'bar'; expect(widget.node.id).to.equal('bar'); }); }); describe('#dataset', () => { it('should get the dataset of the widget node', () => { let widget = new Widget(); expect(widget.dataset).to.equal(widget.node.dataset); }); }); describe('#title', () => { it('should get the title data object for the widget', () => { let widget = new Widget(); expect(widget.title).to.be.an.instanceof(Title); }); }); describe('#parent', () => { it('should default to `null`', () => { let widget = new Widget(); expect(widget.parent).to.equal(null); }); it('should set the parent and send a `child-added` messagee', () => { let child = new Widget(); let parent = new LogWidget(); child.parent = parent; expect(child.parent).to.equal(parent); expect(parent.messages).to.contain('child-added'); }); it('should remove itself from the current parent', () => { let parent0 = new LogWidget(); let parent1 = new LogWidget(); let child = new Widget(); child.parent = parent0; child.parent = parent1; expect(parent0.messages).to.contain('child-removed'); expect(parent1.messages).to.contain('child-added'); }); it('should throw an error if the widget contains the parent', () => { let widget0 = new Widget(); let widget1 = new Widget(); widget0.parent = widget1; expect(() => { widget1.parent = widget0; }).to.throw(Error); }); it('should be a no-op if there is no parent change', () => { let parent = new LogWidget(); let child = new Widget(); child.parent = parent; child.parent = parent; expect(parent.messages).to.not.contain('child-removed'); }); }); describe('#layout', () => { it('should default to `null`', () => { let widget = new Widget(); expect(widget.layout).to.equal(null); }); it('should set the layout for the widget', () => { let widget = new Widget(); let layout = new TestLayout(); widget.layout = layout; expect(widget.layout).to.equal(layout); }); it('should be single-use only', () => { let widget = new Widget(); widget.layout = new TestLayout(); expect(() => { widget.layout = new TestLayout(); }).to.throw(Error); }); it('should be disposed when the widget is disposed', () => { let widget = new Widget(); let layout = new TestLayout(); widget.layout = layout; widget.dispose(); expect(layout.isDisposed).to.equal(true); }); it('should be a no-op if the layout is the same', () => { let widget = new Widget(); let layout = new TestLayout(); widget.layout = layout; widget.layout = layout; expect(widget.layout).to.equal(layout); }); it('should throw an error if the layout already has a parent', () => { let widget0 = new Widget(); let widget1 = new Widget(); let layout = new TestLayout(); widget0.layout = layout; expect(() => { widget1.layout = layout; }).to.throw(Error); }); it('should throw an error if the `DisallowLayout` flag is set', () => { let widget = new Widget(); widget.setFlag(Widget.Flag.DisallowLayout); let layout = new TestLayout(); expect(() => { widget.layout = layout; }).to.throw(Error); }); }); describe('#children()', () => { it('should return an iterator over the widget children', () => { let widget = new Widget(); widget.layout = new TestLayout(); each(widget.children(), child => { expect(child).to.be.an.instanceof(Widget); }); }); it('should return an empty iterator if there is no layout', () => { let widget = new Widget(); expect(widget.children().next()).to.equal(undefined); }); }); describe('#contains()', () => { it('should return `true` if the widget is a descendant', () => { let p1 = new Widget(); let p2 = new Widget(); let p3 = new Widget(); let w1 = new Widget(); let w2 = new Widget(); p2.parent = p1; p3.parent = p2; w1.parent = p2; w2.parent = p3; expect(p1.contains(p1)).to.equal(true); expect(p1.contains(p2)).to.equal(true); expect(p1.contains(p3)).to.equal(true); expect(p1.contains(w1)).to.equal(true); expect(p1.contains(w2)).to.equal(true); expect(p2.contains(p2)).to.equal(true); expect(p2.contains(p3)).to.equal(true); expect(p2.contains(w1)).to.equal(true); expect(p2.contains(w2)).to.equal(true); expect(p3.contains(p3)).to.equal(true); expect(p3.contains(w2)).to.equal(true); }); it('should return `false` if the widget is not a descendant', () => { let p1 = new Widget(); let p2 = new Widget(); let p3 = new Widget(); let w1 = new Widget(); let w2 = new Widget(); p2.parent = p1; p3.parent = p2; w1.parent = p2; w2.parent = p3; expect(p2.contains(p1)).to.equal(false); expect(p3.contains(p1)).to.equal(false); expect(p3.contains(p2)).to.equal(false); expect(p3.contains(w1)).to.equal(false); expect(w1.contains(p1)).to.equal(false); expect(w1.contains(p2)).to.equal(false); expect(w1.contains(p3)).to.equal(false); expect(w1.contains(w2)).to.equal(false); expect(w2.contains(p1)).to.equal(false); expect(w2.contains(p2)).to.equal(false); expect(w2.contains(p3)).to.equal(false); expect(w2.contains(w1)).to.equal(false); }); }); describe('#hasClass()', () => { it('should return `true` if a node has a class', () => { let widget = new Widget(); widget.node.classList.add('foo'); widget.node.classList.add('bar'); widget.node.classList.add('baz'); expect(widget.hasClass('foo')).to.equal(true); expect(widget.hasClass('bar')).to.equal(true); expect(widget.hasClass('baz')).to.equal(true); }); it('should return `false` if a node does not have a class', () => { let widget = new Widget(); widget.node.classList.add('foo'); widget.node.classList.add('bar'); widget.node.classList.add('baz'); expect(widget.hasClass('one')).to.equal(false); expect(widget.hasClass('two')).to.equal(false); expect(widget.hasClass('three')).to.equal(false); }); }); describe('#addClass()', () => { it('should add a class to the DOM node', () => { let widget = new Widget(); expect(widget.node.classList.contains('foo')).to.equal(false); widget.addClass('foo'); expect(widget.node.classList.contains('foo')).to.equal(true); expect(widget.node.classList.contains('bar')).to.equal(false); widget.addClass('bar'); expect(widget.node.classList.contains('bar')).to.equal(true); }); it('should be a no-op if the class is already present', () => { let widget = new Widget(); widget.addClass('foo'); expect(widget.node.classList.contains('foo')).to.equal(true); widget.addClass('foo'); expect(widget.node.classList.contains('foo')).to.equal(true); }); }); describe('#removeClass()', () => { it('should remove the class from the DOM node', () => { let widget = new Widget(); widget.node.classList.add('foo'); widget.node.classList.add('bar'); widget.removeClass('foo'); expect(widget.node.classList.contains('foo')).to.equal(false); expect(widget.node.classList.contains('bar')).to.equal(true); widget.removeClass('bar'); expect(widget.node.classList.contains('bar')).to.equal(false); }); it('should be a no-op if the class is not present', () => { let widget = new Widget(); expect(widget.node.classList.contains('foo')).to.equal(false); widget.removeClass('foo'); expect(widget.node.classList.contains('foo')).to.equal(false); }); }); describe('#toggleClass()', () => { it('should toggle the presence of a class', () => { let widget = new Widget(); widget.toggleClass('foo'); expect(widget.node.classList.contains('foo')).to.equal(true); widget.toggleClass('foo'); expect(widget.node.classList.contains('foo')).to.equal(false); }); it('should force-add a class', () => { let widget = new Widget(); expect(widget.node.classList.contains('foo')).to.equal(false); widget.toggleClass('foo', true); expect(widget.node.classList.contains('foo')).to.equal(true); widget.toggleClass('foo', true); expect(widget.node.classList.contains('foo')).to.equal(true); }); it('should force-remove a class', () => { let widget = new Widget(); widget.node.classList.add('foo'); expect(widget.node.classList.contains('foo')).to.equal(true); widget.toggleClass('foo', false); expect(widget.node.classList.contains('foo')).to.equal(false); widget.toggleClass('foo', false); expect(widget.node.classList.contains('foo')).to.equal(false); }); it('should return `true` if the class is present', () => { let widget = new Widget(); expect(widget.toggleClass('foo')).to.equal(true); expect(widget.toggleClass('foo', true)).to.equal(true); }); it('should return `false` if the class is not present', () => { let widget = new Widget(); widget.node.classList.add('foo'); expect(widget.toggleClass('foo')).to.equal(false); expect(widget.toggleClass('foo', false)).to.equal(false); }); }); describe('#update()', () => { it('should post an `update-request` message', done => { let widget = new LogWidget(); widget.update(); expect(widget.messages).to.deep.equal([]); requestAnimationFrame(() => { expect(widget.messages).to.deep.equal(['update-request']); done(); }); }); }); describe('#fit()', () => { it('should post a `fit-request` message to the widget', done => { let widget = new LogWidget(); widget.fit(); expect(widget.messages).to.deep.equal([]); requestAnimationFrame(() => { expect(widget.messages).to.deep.equal(['fit-request']); done(); }); }); }); describe('#activate()', () => { it('should post an `activate-request` message', done => { let widget = new LogWidget(); widget.activate(); expect(widget.messages).to.deep.equal([]); requestAnimationFrame(() => { expect(widget.messages).to.deep.equal(['activate-request']); done(); }); }); }); describe('#close()', () => { it('should send a `close-request` message', () => { let widget = new LogWidget(); expect(widget.messages).to.deep.equal([]); widget.close(); expect(widget.messages).to.deep.equal(['close-request']); }); }); describe('#show()', () => { it('should set `isHidden` to `false`', () => { let widget = new Widget(); widget.hide(); expect(widget.isHidden).to.equal(true); widget.show(); expect(widget.isHidden).to.equal(false); }); it('should remove the "lm-mod-hidden" class', () => { let widget = new Widget(); widget.hide(); expect(widget.hasClass('lm-mod-hidden')).to.equal(true); widget.show(); expect(widget.hasClass('lm-mod-hidden')).to.equal(false); }); it('should send an `after-show` message if applicable', () => { let widget = new LogWidget(); widget.hide(); Widget.attach(widget, document.body); widget.show(); expect(widget.messages).to.contains('after-show'); widget.dispose(); }); it('should send a `child-shown` message to the parent', () => { let parent = new LogWidget(); let child = new Widget(); child.parent = parent; child.hide(); child.show(); expect(parent.messages).to.contains('child-shown'); }); it('should be a no-op if not hidden', () => { let widget = new LogWidget(); Widget.attach(widget, document.body); widget.show(); expect(widget.messages).to.not.contains('after-show'); widget.dispose(); }); }); describe('#hide()', () => { it('should hide the widget', () => { let widget = new Widget(); widget.hide(); expect(widget.isHidden).to.equal(true); }); it('should add the `lm-mod-hidden` class', () => { let widget = new Widget(); widget.hide(); expect(widget.hasClass('lm-mod-hidden')).to.equal(true); }); it('should send a `before-hide` message if applicable', () => { let widget = new LogWidget(); Widget.attach(widget, document.body); widget.hide(); expect(widget.messages).to.contain('before-hide'); widget.dispose(); }); it('should send a `child-hidden` message to the parent', () => { let parent = new LogWidget(); let child = new Widget(); child.parent = parent; child.hide(); expect(parent.messages).to.contain('child-hidden'); }); it('should be a no-op if already hidden', () => { let widget = new LogWidget(); widget.hide(); Widget.attach(widget, document.body); widget.hide(); expect(widget.messages).to.not.contain('before-hide'); widget.dispose(); }); }); describe('#setHidden()', () => { it('should call hide if `hidden = true`', () => { let widget = new LogWidget(); Widget.attach(widget, document.body); widget.setHidden(true); expect(widget.isHidden).to.equal(true); expect(widget.messages).to.contain('before-hide'); widget.dispose(); }); it('should call show if `hidden = false`', () => { let widget = new LogWidget(); widget.hide(); Widget.attach(widget, document.body); widget.setHidden(false); expect(widget.isHidden).to.equal(false); expect(widget.messages).to.contain('after-show'); widget.dispose(); }); }); describe('#hiddenMode', () => { it('should use class to hide the widget by default', () => { let widget = new Widget(); Widget.attach(widget, document.body); widget.hide(); expect(widget.hasClass('lm-mod-hidden')).to.equal(true); expect(widget.node.style.transform).to.be.equal(''); expect(widget.node.style.willChange).to.not.equal('transform'); widget.dispose(); }); it('should use transformation if in "scale" mode', () => { let widget = new Widget(); Widget.attach(widget, document.body); widget.hiddenMode = Widget.HiddenMode.Scale; widget.hide(); expect(widget.hasClass('lm-mod-hidden')).to.equal(false); expect(widget.node.style.transform).to.equal('scale(0)'); expect(widget.node.style.willChange).to.equal('transform'); widget.dispose(); }); it('should remove class when switching from display to scale', () => { let widget = new Widget(); Widget.attach(widget, document.body); widget.hide(); widget.hiddenMode = Widget.HiddenMode.Scale; expect(widget.hasClass('lm-mod-hidden')).to.equal(false); expect(widget.node.style.transform).to.equal('scale(0)'); expect(widget.node.style.willChange).to.equal('transform'); widget.dispose(); }); it('should add class when switching from scale to display', () => { let widget = new Widget(); Widget.attach(widget, document.body); widget.hiddenMode = Widget.HiddenMode.Scale; widget.hide(); widget.hiddenMode = Widget.HiddenMode.Display; expect(widget.hasClass('lm-mod-hidden')).to.equal(true); expect(widget.node.style.transform).to.equal(''); expect(widget.node.style.willChange).to.equal('auto'); widget.dispose(); }); }); describe('#testFlag()', () => { it('should test whether the given widget flag is set', () => { let widget = new Widget(); expect(widget.testFlag(Widget.Flag.IsHidden)).to.equal(false); }); }); describe('#setFlag()', () => { it('should set the given widget flag', () => { let widget = new Widget(); widget.setFlag(Widget.Flag.IsHidden); expect(widget.testFlag(Widget.Flag.IsHidden)).to.equal(true); }); }); describe('#clearFlag()', () => { it('should clear the given widget flag', () => { let widget = new Widget(); widget.setFlag(Widget.Flag.IsHidden); widget.clearFlag(Widget.Flag.IsHidden); expect(widget.testFlag(Widget.Flag.IsHidden)).to.equal(false); }); }); describe('#notifyLayout()', () => { it("should send a message to the widget's layout", () => { let child = new LogWidget(); let parent = new LogWidget(); let layout = new TestLayout(); parent.layout = layout; child.parent = parent; expect(parent.methods).to.contain('notifyLayout'); }); }); describe('#onActivateRequest()', () => { it('should be invoked on an `activate-request', () => { let widget = new LogWidget(); MessageLoop.sendMessage(widget, Widget.Msg.ActivateRequest); expect(widget.methods).to.contain('onActivateRequest'); }); it('should notify the layout', () => { let widget = new LogWidget(); MessageLoop.sendMessage(widget, Widget.Msg.ActivateRequest); expect(widget.methods).to.contain('notifyLayout'); }); }); describe('#onCloseRequest()', () => { it('should be invoked on a `close-request`', () => { let widget = new LogWidget(); MessageLoop.sendMessage(widget, Widget.Msg.CloseRequest); expect(widget.messages).to.contain('close-request'); expect(widget.methods).to.contain('onCloseRequest'); }); it('should unparent a child widget by default', () => { let parent = new Widget(); let child = new Widget(); child.parent = parent; MessageLoop.sendMessage(child, Widget.Msg.CloseRequest); expect(child.parent).to.equal(null); }); it('should detach a root widget by default', () => { let widget = new Widget(); Widget.attach(widget, document.body); MessageLoop.sendMessage(widget, Widget.Msg.CloseRequest); expect(widget.isAttached).to.equal(false); }); it('should notify the layout', () => { let widget = new LogWidget(); MessageLoop.sendMessage(widget, Widget.Msg.CloseRequest); expect(widget.methods).to.contain('notifyLayout'); }); }); describe('#onResize()', () => { it('should be invoked when the widget is resized', () => { let widget = new LogWidget(); Widget.attach(widget, document.body); MessageLoop.sendMessage(widget, Widget.ResizeMessage.UnknownSize); expect(widget.messages).to.contain('resize'); expect(widget.methods).to.contain('onResize'); widget.dispose(); }); it('should notify the layout', () => { let widget = new LogWidget(); Widget.attach(widget, document.body); MessageLoop.sendMessage(widget, Widget.ResizeMessage.UnknownSize); expect(widget.methods).to.contain('notifyLayout'); widget.dispose(); }); }); describe('#onUpdateRequest()', () => { it('should be invoked when an update is requested', () => { let widget = new LogWidget(); MessageLoop.sendMessage(widget, Widget.Msg.UpdateRequest); expect(widget.messages).to.contain('update-request'); expect(widget.methods).to.contain('onUpdateRequest'); }); it('should notify the layout', () => { let widget = new LogWidget(); MessageLoop.sendMessage(widget, Widget.Msg.UpdateRequest); expect(widget.methods).to.contain('notifyLayout'); }); }); describe('#onAfterShow()', () => { it('should be invoked just after the widget is made visible', () => { let widget = new LogWidget(); Widget.attach(widget, document.body); widget.hide(); widget.show(); expect(widget.messages).to.contain('after-show'); expect(widget.methods).to.contain('onAfterShow'); widget.dispose(); }); it('should set the `isVisible` flag', () => { let widget = new LogWidget(); Widget.attach(widget, document.body); widget.hide(); expect(widget.testFlag(Widget.Flag.IsVisible)).to.equal(false); widget.show(); expect(widget.testFlag(Widget.Flag.IsVisible)).to.equal(true); widget.dispose(); }); it('should notify the layout', () => { let widget = new LogWidget(); Widget.attach(widget, document.body); widget.hide(); widget.show(); expect(widget.methods).to.contain('notifyLayout'); widget.dispose(); }); }); describe('#onBeforeHide()', () => { it('should be invoked just before the widget is made not-visible', () => { let widget = new LogWidget(); Widget.attach(widget, document.body); widget.hide(); expect(widget.messages).to.contain('before-hide'); expect(widget.methods).to.contain('onBeforeHide'); widget.dispose(); }); it('should clear the `isVisible` flag', () => { let widget = new LogWidget(); Widget.attach(widget, document.body); expect(widget.testFlag(Widget.Flag.IsVisible)).to.equal(true); widget.hide(); expect(widget.testFlag(Widget.Flag.IsVisible)).to.equal(false); widget.dispose(); }); it('should notify the layout', () => { let widget = new LogWidget(); Widget.attach(widget, document.body); widget.hide(); expect(widget.methods).to.contain('notifyLayout'); widget.dispose(); }); }); describe('#onAfterAttach()', () => { it('should be invoked just after the widget is attached', () => { let widget = new LogWidget(); Widget.attach(widget, document.body); expect(widget.messages).to.contain('after-attach'); expect(widget.methods).to.contain('onAfterAttach'); widget.dispose(); }); it('should set the visible flag if warranted', () => { let widget = new LogWidget(); Widget.attach(widget, document.body); expect(widget.testFlag(Widget.Flag.IsVisible)).to.equal(true); Widget.detach(widget); widget.hide(); Widget.attach(widget, document.body); expect(widget.testFlag(Widget.Flag.IsVisible)).to.equal(false); Widget.detach(widget); let child = new LogWidget(); child.parent = widget; widget.show(); Widget.attach(widget, document.body); expect(widget.testFlag(Widget.Flag.IsVisible)).to.equal(true); Widget.detach(widget); }); it('should notify the layout', () => { let widget = new LogWidget(); Widget.attach(widget, document.body); expect(widget.methods).to.contain('notifyLayout'); widget.dispose(); }); }); describe('#onBeforeDetach()', () => { it('should be invoked just before the widget is detached', () => { let widget = new LogWidget(); Widget.attach(widget, document.body); Widget.detach(widget); expect(widget.messages).to.contain('before-detach'); expect(widget.methods).to.contain('onBeforeDetach'); }); it('should clear the `isVisible` and `isAttached` flags', () => { let widget = new LogWidget(); Widget.attach(widget, document.body); expect(widget.testFlag(Widget.Flag.IsVisible)).to.equal(true); expect(widget.testFlag(Widget.Flag.IsAttached)).to.equal(true); Widget.detach(widget); expect(widget.testFlag(Widget.Flag.IsVisible)).to.equal(false); expect(widget.testFlag(Widget.Flag.IsAttached)).to.equal(false); }); it('should notify the layout', () => { let widget = new LogWidget(); Widget.attach(widget, document.body); Widget.detach(widget); expect(widget.methods).to.contain('notifyLayout'); }); }); describe('#onChildAdded()', () => { it('should be invoked when a child is added', () => { let child = new Widget(); let parent = new LogWidget(); child.parent = parent; expect(parent.methods).to.contain('onChildAdded'); }); it('should notify the layout', () => { let child = new Widget(); let parent = new LogWidget(); child.parent = parent; expect(parent.methods).to.contain('notifyLayout'); }); context('`msg` parameter', () => { it('should be a `ChildMessage`', () => { let child = new Widget(); let parent = new LogWidget(); child.parent = parent; expect(parent.raw[0]).to.be.an.instanceof(Widget.ChildMessage); }); it('should have a `type` of `child-added`', () => { let child = new Widget(); let parent = new LogWidget(); child.parent = parent; expect(parent.raw[0].type).to.equal('child-added'); }); it('should have the correct `child`', () => { let child = new Widget(); let parent = new LogWidget(); child.parent = parent; expect((parent.raw[0] as Widget.ChildMessage).child).to.equal(child); }); }); }); describe('#onChildRemoved()', () => { it('should be invoked when a child is removed', () => { let child = new Widget(); let parent = new LogWidget(); child.parent = parent; child.parent = null; expect(parent.methods).to.contain('onChildRemoved'); }); it('should notify the layout', () => { let child = new Widget(); let parent = new LogWidget(); child.parent = parent; parent.methods = []; child.parent = null; expect(parent.methods).to.contain('notifyLayout'); }); context('`msg` parameter', () => { it('should be a `ChildMessage`', () => { let child = new Widget(); let parent = new LogWidget(); child.parent = parent; parent.raw = []; child.parent = null; expect(parent.raw[0]).to.be.an.instanceof(Widget.ChildMessage); }); it('should have a `type` of `child-removed`', () => { let child = new Widget(); let parent = new LogWidget(); child.parent = parent; parent.raw = []; child.parent = null; expect((parent.raw[0] as Widget.ChildMessage).type).to.equal( 'child-removed' ); }); it('should have the correct `child`', () => { let child = new Widget(); let parent = new LogWidget(); child.parent = parent; parent.raw = []; child.parent = null; expect((parent.raw[0] as Widget.ChildMessage).child).to.equal(child); }); }); }); describe('.ChildMessage', () => { describe('#constructor()', () => { it('should accept the message type and child widget', () => { let msg = new Widget.ChildMessage('test', new Widget()); expect(msg).to.be.an.instanceof(Widget.ChildMessage); }); }); describe('#child', () => { it('should be the child passed to the constructor', () => { let widget = new Widget(); let msg = new Widget.ChildMessage('test', widget); expect(msg.child).to.equal(widget); }); }); }); describe('.ResizeMessage', () => { describe('#constructor()', () => { it('should accept a width and height', () => { let msg = new Widget.ResizeMessage(100, 100); expect(msg).to.be.an.instanceof(Widget.ResizeMessage); }); }); describe('#width', () => { it('should be the width passed to the constructor', () => { let msg = new Widget.ResizeMessage(100, 200); expect(msg.width).to.equal(100); }); }); describe('#height', () => { it('should be the height passed to the constructor', () => { let msg = new Widget.ResizeMessage(100, 200); expect(msg.height).to.equal(200); }); }); describe('.UnknownSize', () => { it('should be a `ResizeMessage`', () => { let msg = Widget.ResizeMessage.UnknownSize; expect(msg).to.be.an.instanceof(Widget.ResizeMessage); }); it('should have a `width` of `-1`', () => { let msg = Widget.ResizeMessage.UnknownSize; expect(msg.width).to.equal(-1); }); it('should have a `height` of `-1`', () => { let msg = Widget.ResizeMessage.UnknownSize; expect(msg.height).to.equal(-1); }); }); }); describe('.attach()', () => { it('should attach a root widget to a host', () => { let widget = new Widget(); expect(widget.isAttached).to.equal(false); Widget.attach(widget, document.body); expect(widget.isAttached).to.equal(true); widget.dispose(); }); it('should throw if the widget is not a root', () => { let parent = new Widget(); let child = new Widget(); child.parent = parent; expect(() => { Widget.attach(child, document.body); }).to.throw(Error); }); it('should throw if the widget is already attached', () => { let widget = new Widget(); Widget.attach(widget, document.body); expect(() => { Widget.attach(widget, document.body); }).to.throw(Error); widget.dispose(); }); it('should throw if the host is not attached to the DOM', () => { let widget = new Widget(); let host = document.createElement('div'); expect(() => { Widget.attach(widget, host); }).to.throw(Error); }); it('should dispatch an `after-attach` message', () => { let widget = new LogWidget(); expect(widget.isAttached).to.equal(false); expect(widget.messages).to.not.contain('after-attach'); Widget.attach(widget, document.body); expect(widget.isAttached).to.equal(true); expect(widget.messages).to.contain('after-attach'); widget.dispose(); }); }); describe('.detach()', () => { it('should detach a root widget from its host', () => { let widget = new Widget(); Widget.attach(widget, document.body); expect(widget.isAttached).to.equal(true); Widget.detach(widget); expect(widget.isAttached).to.equal(false); widget.dispose(); }); it('should throw if the widget is not a root', () => { let parent = new Widget(); let child = new Widget(); child.parent = parent; Widget.attach(parent, document.body); expect(() => { Widget.detach(child); }).to.throw(Error); parent.dispose(); }); it('should throw if the widget is not attached', () => { let widget = new Widget(); expect(() => { Widget.detach(widget); }).to.throw(Error); }); it('should dispatch a `before-detach` message', () => { let widget = new LogWidget(); Widget.attach(widget, document.body); widget.messages = []; Widget.detach(widget); expect(widget.messages).to.contain('before-detach'); widget.dispose(); }); }); }); }); lumino-2021.12.13/packages/widgets/tests/tsconfig.json000066400000000000000000000015731415564225700225240ustar00rootroot00000000000000{ "compilerOptions": { "declaration": false, "noImplicitAny": true, "noEmitOnError": true, "noUnusedLocals": true, "strictNullChecks": true, "module": "commonjs", "moduleResolution": "node", "target": "ES5", "outDir": "build", "lib": ["ES5", "DOM"], "types": ["chai", "mocha"], "rootDir": "src" }, "include": ["src/*"], "references": [ { "path": "../../algorithm" }, { "path": "../../commands" }, { "path": "../../coreutils" }, { "path": "../../disposable" }, { "path": "../../domutils" }, { "path": "../../dragdrop" }, { "path": "../../keyboard" }, { "path": "../../messaging" }, { "path": "../../properties" }, { "path": "../../signaling" }, { "path": "../../virtualdom" } ] } lumino-2021.12.13/packages/widgets/tests/webpack.config.js000066400000000000000000000004761415564225700232340ustar00rootroot00000000000000var path = require('path'); module.exports = { entry: './build/index.spec.js', mode: 'development', output: { filename: './build/bundle.test.js', path: path.resolve(__dirname) }, module: { rules: [ { test: /\.css$/, use: ['style-loader', 'css-loader'] } ] } }; lumino-2021.12.13/packages/widgets/tsconfig.json000066400000000000000000000020471415564225700213570ustar00rootroot00000000000000{ "compilerOptions": { "composite": true, "sourceMap": true, "declaration": true, "declarationDir": "./types", "declarationMap": true, "noImplicitAny": true, "noEmitOnError": true, "noUnusedLocals": true, "strictNullChecks": true, "module": "ES6", "moduleResolution": "node", "target": "ES5", "outDir": "lib", "lib": [ "ES5", "ES2015.Collection", "ES2015.Promise", "ES2015.Iterable", "DOM" ], "importHelpers": true, "types": [], "rootDir": "src" }, "include": ["src/*"], "references": [ { "path": "../algorithm" }, { "path": "../commands" }, { "path": "../coreutils" }, { "path": "../disposable" }, { "path": "../domutils" }, { "path": "../dragdrop" }, { "path": "../keyboard" }, { "path": "../messaging" }, { "path": "../properties" }, { "path": "../signaling" }, { "path": "../virtualdom" } ] } lumino-2021.12.13/review/000077500000000000000000000000001415564225700147225ustar00rootroot00000000000000lumino-2021.12.13/review/api/000077500000000000000000000000001415564225700154735ustar00rootroot00000000000000lumino-2021.12.13/review/api/algorithm.api.md000066400000000000000000000251701415564225700205600ustar00rootroot00000000000000## API Report File for "@lumino/algorithm" > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts // @public export namespace ArrayExt { export function fill(array: MutableArrayLike, value: T, start?: number, stop?: number): void; export function findFirstIndex(array: ArrayLike, fn: (value: T, index: number) => boolean, start?: number, stop?: number): number; export function findFirstValue(array: ArrayLike, fn: (value: T, index: number) => boolean, start?: number, stop?: number): T | undefined; export function findLastIndex(array: ArrayLike, fn: (value: T, index: number) => boolean, start?: number, stop?: number): number; export function findLastValue(array: ArrayLike, fn: (value: T, index: number) => boolean, start?: number, stop?: number): T | undefined; export function firstIndexOf(array: ArrayLike, value: T, start?: number, stop?: number): number; export function insert(array: Array, index: number, value: T): void; export function lastIndexOf(array: ArrayLike, value: T, start?: number, stop?: number): number; export function lowerBound(array: ArrayLike, value: U, fn: (element: T, value: U) => number, start?: number, stop?: number): number; export function move(array: MutableArrayLike, fromIndex: number, toIndex: number): void; export type MutableArrayLike = { readonly length: number; [index: number]: T; }; export function removeAllOf(array: Array, value: T, start?: number, stop?: number): number; export function removeAllWhere(array: Array, fn: (value: T, index: number) => boolean, start?: number, stop?: number): number; export function removeAt(array: Array, index: number): T | undefined; export function removeFirstOf(array: Array, value: T, start?: number, stop?: number): number; export function removeFirstWhere(array: Array, fn: (value: T, index: number) => boolean, start?: number, stop?: number): { index: number; value: T | undefined; }; export function removeLastOf(array: Array, value: T, start?: number, stop?: number): number; export function removeLastWhere(array: Array, fn: (value: T, index: number) => boolean, start?: number, stop?: number): { index: number; value: T | undefined; }; export function reverse(array: MutableArrayLike, start?: number, stop?: number): void; export function rotate(array: MutableArrayLike, delta: number, start?: number, stop?: number): void; export function shallowEqual(a: ArrayLike, b: ArrayLike, fn?: (a: T, b: T) => boolean): boolean; export function slice(array: ArrayLike, options?: slice.IOptions): T[]; export namespace slice { export interface IOptions { start?: number; step?: number; stop?: number; } } export function upperBound(array: ArrayLike, value: U, fn: (element: T, value: U) => number, start?: number, stop?: number): number; } // @public export class ArrayIterator implements IIterator { constructor(source: ArrayLike); clone(): IIterator; iter(): IIterator; next(): T | undefined; } // @public export function chain(...objects: IterableOrArrayLike[]): IIterator; // @public export class ChainIterator implements IIterator { constructor(source: IIterator>); clone(): IIterator; iter(): IIterator; next(): T | undefined; } // @public export function each(object: IterableOrArrayLike, fn: (value: T, index: number) => boolean | void): void; // @public export function empty(): IIterator; // @public export class EmptyIterator implements IIterator { constructor(); clone(): IIterator; iter(): IIterator; next(): T | undefined; } // @public export function enumerate(object: IterableOrArrayLike, start?: number): IIterator<[number, T]>; // @public export class EnumerateIterator implements IIterator<[number, T]> { constructor(source: IIterator, start: number); clone(): IIterator<[number, T]>; iter(): IIterator<[number, T]>; next(): [number, T] | undefined; } // @public export function every(object: IterableOrArrayLike, fn: (value: T, index: number) => boolean): boolean; // @public export function filter(object: IterableOrArrayLike, fn: (value: T, index: number) => boolean): IIterator; // @public export class FilterIterator implements IIterator { constructor(source: IIterator, fn: (value: T, index: number) => boolean); clone(): IIterator; iter(): IIterator; next(): T | undefined; } // @public export function find(object: IterableOrArrayLike, fn: (value: T, index: number) => boolean): T | undefined; // @public export function findIndex(object: IterableOrArrayLike, fn: (value: T, index: number) => boolean): number; // @public export class FnIterator implements IIterator { constructor(fn: () => T | undefined); clone(): IIterator; iter(): IIterator; next(): T | undefined; } // @public export interface IIterable { iter(): IIterator; } // @public export interface IIterator extends IIterable { clone(): IIterator; next(): T | undefined; } // @public export interface IRetroable { retro(): IIterator; } // @public export class ItemIterator implements IIterator<[string, T]> { constructor(source: { readonly [key: string]: T; }, keys?: string[]); clone(): IIterator<[string, T]>; iter(): IIterator<[string, T]>; next(): [string, T] | undefined; } // @public export function iter(object: IterableOrArrayLike): IIterator; // @public export type IterableOrArrayLike = IIterable | ArrayLike; // @public export function iterFn(fn: () => T | undefined): IIterator; // @public export function iterItems(object: { readonly [key: string]: T; }): IIterator<[string, T]>; // @public export function iterKeys(object: { readonly [key: string]: T; }): IIterator; // @public export function iterValues(object: { readonly [key: string]: T; }): IIterator; // @public export class KeyIterator implements IIterator { constructor(source: { readonly [key: string]: any; }, keys?: string[]); clone(): IIterator; iter(): IIterator; next(): string | undefined; } // @public export function map(object: IterableOrArrayLike, fn: (value: T, index: number) => U): IIterator; // @public export class MapIterator implements IIterator { constructor(source: IIterator, fn: (value: T, index: number) => U); clone(): IIterator; iter(): IIterator; next(): U | undefined; } // @public export function max(object: IterableOrArrayLike, fn: (first: T, second: T) => number): T | undefined; // @public export function min(object: IterableOrArrayLike, fn: (first: T, second: T) => number): T | undefined; // @public export function minmax(object: IterableOrArrayLike, fn: (first: T, second: T) => number): [T, T] | undefined; // @public export function once(value: T): IIterator; // @public export function range(start: number, stop?: number, step?: number): IIterator; // @public export class RangeIterator implements IIterator { constructor(start: number, stop: number, step: number); clone(): IIterator; iter(): IIterator; next(): number | undefined; } // @public export function reduce(object: IterableOrArrayLike, fn: (accumulator: T, value: T, index: number) => T): T; // @public (undocumented) export function reduce(object: IterableOrArrayLike, fn: (accumulator: U, value: T, index: number) => U, initial: U): U; // @public export function repeat(value: T, count: number): IIterator; // @public export class RepeatIterator implements IIterator { constructor(value: T, count: number); clone(): IIterator; iter(): IIterator; next(): T | undefined; } // @public export function retro(object: RetroableOrArrayLike): IIterator; // @public export type RetroableOrArrayLike = IRetroable | ArrayLike; // @public export class RetroArrayIterator implements IIterator { constructor(source: ArrayLike); clone(): IIterator; iter(): IIterator; next(): T | undefined; } // @public export function some(object: IterableOrArrayLike, fn: (value: T, index: number) => boolean): boolean; // @public export function stride(object: IterableOrArrayLike, step: number): IIterator; // @public export class StrideIterator implements IIterator { constructor(source: IIterator, step: number); clone(): IIterator; iter(): IIterator; next(): T | undefined; } // @public export namespace StringExt { export function cmp(a: string, b: string): number; export function findIndices(source: string, query: string, start?: number): number[] | null; export function highlight(source: string, indices: ReadonlyArray, fn: (chunk: string) => T): Array; export interface IMatchResult { indices: number[]; score: number; } export function matchSumOfDeltas(source: string, query: string, start?: number): IMatchResult | null; export function matchSumOfSquares(source: string, query: string, start?: number): IMatchResult | null; } // @public export function take(object: IterableOrArrayLike, count: number): IIterator; // @public export class TakeIterator implements IIterator { constructor(source: IIterator, count: number); clone(): IIterator; iter(): IIterator; next(): T | undefined; } // @public export function toArray(object: IterableOrArrayLike): T[]; // @public export function toObject(object: IterableOrArrayLike<[string, T]>): { [key: string]: T; }; // @public export function topologicSort(edges: IterableOrArrayLike<[T, T]>): T[]; // @public export class ValueIterator implements IIterator { constructor(source: { readonly [key: string]: T; }, keys?: string[]); clone(): IIterator; iter(): IIterator; next(): T | undefined; } // @public export function zip(...objects: IterableOrArrayLike[]): IIterator; // @public export class ZipIterator implements IIterator { constructor(source: IIterator[]); clone(): IIterator; iter(): IIterator; next(): T[] | undefined; } // (No @packageDocumentation comment for this package) ``` lumino-2021.12.13/review/api/application.api.md000066400000000000000000000035671415564225700211030ustar00rootroot00000000000000## API Report File for "@lumino/application" > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts import { CommandRegistry } from '@lumino/commands'; import { ContextMenu } from '@lumino/widgets'; import { Menu } from '@lumino/widgets'; import { Token } from '@lumino/coreutils'; import { Widget } from '@lumino/widgets'; // @public export class Application { constructor(options: Application.IOptions); activatePlugin(id: string): Promise; protected addEventListeners(): void; protected attachShell(id: string): void; readonly commands: CommandRegistry; readonly contextMenu: ContextMenu; protected evtContextMenu(event: MouseEvent): void; protected evtKeydown(event: KeyboardEvent): void; protected evtResize(event: Event): void; handleEvent(event: Event): void; hasPlugin(id: string): boolean; listPlugins(): string[]; registerPlugin(plugin: IPlugin): void; registerPlugins(plugins: IPlugin[]): void; resolveOptionalService(token: Token): Promise; resolveRequiredService(token: Token): Promise; readonly shell: T; start(options?: Application.IStartOptions): Promise; readonly started: Promise; } // @public export namespace Application { export interface IOptions { contextMenuRenderer?: Menu.IRenderer; shell: T; } export interface IStartOptions { hostID?: string; ignorePlugins?: string[]; startPlugins?: string[]; } } // @public export interface IPlugin { activate: (app: T, ...args: any[]) => U | Promise; autoStart?: boolean; id: string; optional?: Token[]; provides?: Token; requires?: Token[]; } // (No @packageDocumentation comment for this package) ``` lumino-2021.12.13/review/api/collections.api.md000066400000000000000000000073221415564225700211070ustar00rootroot00000000000000## API Report File for "@lumino/collections" > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts import { IIterable } from '@lumino/algorithm'; import { IIterator } from '@lumino/algorithm'; import { IRetroable } from '@lumino/algorithm'; import { IterableOrArrayLike } from '@lumino/algorithm'; // @public export class BPlusTree implements IIterable, IRetroable { constructor(cmp: (a: T, b: T) => number); assign(items: IterableOrArrayLike): void; at(index: number): T | undefined; clear(): void; readonly cmp: (a: T, b: T) => number; delete(key: U, cmp: (item: T, key: U) => number): T | undefined; readonly first: T | undefined; get(key: U, cmp: (item: T, key: U) => number): T | undefined; has(key: U, cmp: (item: T, key: U) => number): boolean; indexOf(key: U, cmp: (item: T, key: U) => number): number; insert(item: T): T | undefined; readonly isEmpty: boolean; iter(): IIterator; readonly last: T | undefined; remove(index: number): T | undefined; retro(): IIterator; retroSlice(start?: number, stop?: number): IIterator; readonly size: number; slice(start?: number, stop?: number): IIterator; update(items: IterableOrArrayLike): void; } // @public export namespace BPlusTree { export function from(items: IterableOrArrayLike, cmp: (a: T, b: T) => number): BPlusTree; } // @public export class LinkedList implements IIterable, IRetroable { constructor(); addFirst(value: T): LinkedList.INode; addLast(value: T): LinkedList.INode; assign(values: IterableOrArrayLike): void; clear(): void; readonly first: T | undefined; readonly firstNode: LinkedList.INode | null; insertAfter(value: T, ref: LinkedList.INode | null): LinkedList.INode; insertBefore(value: T, ref: LinkedList.INode | null): LinkedList.INode; readonly isEmpty: boolean; iter(): IIterator; readonly last: T | undefined; readonly lastNode: LinkedList.INode | null; readonly length: number; nodes(): IIterator>; pop(): T | undefined; push(value: T): void; removeFirst(): T | undefined; removeLast(): T | undefined; removeNode(node: LinkedList.INode): void; retro(): IIterator; retroNodes(): IIterator>; shift(value: T): void; readonly size: number; unshift(): T | undefined; } // @public export namespace LinkedList { export class ForwardNodeIterator implements IIterator> { constructor(node: INode | null); clone(): IIterator>; iter(): IIterator>; next(): INode | undefined; } export class ForwardValueIterator implements IIterator { constructor(node: INode | null); clone(): IIterator; iter(): IIterator; next(): T | undefined; } export function from(values: IterableOrArrayLike): LinkedList; export interface INode { readonly list: LinkedList | null; readonly next: INode | null; readonly prev: INode | null; readonly value: T; } export class RetroNodeIterator implements IIterator> { constructor(node: INode | null); clone(): IIterator>; iter(): IIterator>; next(): INode | undefined; } export class RetroValueIterator implements IIterator { constructor(node: INode | null); clone(): IIterator; iter(): IIterator; next(): T | undefined; } } // (No @packageDocumentation comment for this package) ``` lumino-2021.12.13/review/api/commands.api.md000066400000000000000000000101651415564225700203710ustar00rootroot00000000000000## API Report File for "@lumino/commands" > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts import { IDisposable } from '@lumino/disposable'; import { ISignal } from '@lumino/signaling'; import { ReadonlyJSONObject } from '@lumino/coreutils'; // @public export class CommandRegistry { constructor(); addCommand(id: string, options: CommandRegistry.ICommandOptions): IDisposable; addKeyBinding(options: CommandRegistry.IKeyBindingOptions): IDisposable; caption(id: string, args?: ReadonlyJSONObject): string; className(id: string, args?: ReadonlyJSONObject): string; readonly commandChanged: ISignal; readonly commandExecuted: ISignal; dataset(id: string, args?: ReadonlyJSONObject): CommandRegistry.Dataset; execute(id: string, args?: ReadonlyJSONObject): Promise; hasCommand(id: string): boolean; // @deprecated (undocumented) icon(id: string, args?: ReadonlyJSONObject): string; iconClass(id: string, args?: ReadonlyJSONObject): string; iconLabel(id: string, args?: ReadonlyJSONObject): string; isEnabled(id: string, args?: ReadonlyJSONObject): boolean; isToggled(id: string, args?: ReadonlyJSONObject): boolean; isVisible(id: string, args?: ReadonlyJSONObject): boolean; readonly keyBindingChanged: ISignal; readonly keyBindings: ReadonlyArray; label(id: string, args?: ReadonlyJSONObject): string; listCommands(): string[]; mnemonic(id: string, args?: ReadonlyJSONObject): number; notifyCommandChanged(id?: string): void; processKeydownEvent(event: KeyboardEvent): void; usage(id: string, args?: ReadonlyJSONObject): string; } // @public export namespace CommandRegistry { export type CommandFunc = (args: ReadonlyJSONObject) => T; export type Dataset = { readonly [key: string]: string; }; export function formatKeystroke(keystroke: string): string; export interface ICommandChangedArgs { readonly id: string | undefined; readonly type: 'added' | 'removed' | 'changed' | 'many-changed'; } export interface ICommandExecutedArgs { readonly args: ReadonlyJSONObject; readonly id: string; readonly result: Promise; } export interface ICommandOptions { caption?: string | CommandFunc; className?: string | CommandFunc; dataset?: Dataset | CommandFunc; execute: CommandFunc>; // @deprecated (undocumented) icon?: string | CommandFunc; iconClass?: string | CommandFunc; iconLabel?: string | CommandFunc; isEnabled?: CommandFunc; isToggled?: CommandFunc; isVisible?: CommandFunc; label?: string | CommandFunc; mnemonic?: number | CommandFunc; usage?: string | CommandFunc; } export interface IKeyBinding { readonly args: ReadonlyJSONObject; readonly command: string; readonly keys: ReadonlyArray; readonly selector: string; } export interface IKeyBindingChangedArgs { readonly binding: IKeyBinding; readonly type: 'added' | 'removed'; } export interface IKeyBindingOptions { args?: ReadonlyJSONObject; command: string; keys: string[]; linuxKeys?: string[]; macKeys?: string[]; selector: string; winKeys?: string[]; } export interface IKeystrokeParts { alt: boolean; cmd: boolean; ctrl: boolean; key: string; shift: boolean; } export function keystrokeForKeydownEvent(event: KeyboardEvent): string; export function normalizeKeys(options: IKeyBindingOptions): string[]; export function normalizeKeystroke(keystroke: string): string; export function parseKeystroke(keystroke: string): IKeystrokeParts; } // (No @packageDocumentation comment for this package) ``` lumino-2021.12.13/review/api/coreutils.api.md000066400000000000000000000065111415564225700206010ustar00rootroot00000000000000## API Report File for "@lumino/coreutils" > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts // @public export interface JSONArray extends Array { } // @public export namespace JSONExt { const emptyObject: ReadonlyJSONObject; const emptyArray: ReadonlyJSONArray; export function deepCopy(value: T): T; export function deepEqual(first: ReadonlyPartialJSONValue, second: ReadonlyPartialJSONValue): boolean; export function isArray(value: JSONValue): value is JSONArray; // (undocumented) export function isArray(value: ReadonlyJSONValue): value is ReadonlyJSONArray; // (undocumented) export function isArray(value: PartialJSONValue): value is PartialJSONArray; // (undocumented) export function isArray(value: ReadonlyPartialJSONValue): value is ReadonlyPartialJSONArray; export function isObject(value: JSONValue): value is JSONObject; // (undocumented) export function isObject(value: ReadonlyJSONValue): value is ReadonlyJSONObject; // (undocumented) export function isObject(value: PartialJSONValue): value is PartialJSONObject; // (undocumented) export function isObject(value: ReadonlyPartialJSONValue): value is ReadonlyPartialJSONObject; export function isPrimitive(value: ReadonlyPartialJSONValue): value is JSONPrimitive; } // @public export interface JSONObject { // (undocumented) [key: string]: JSONValue; } // @public export type JSONPrimitive = boolean | number | string | null; // @public export type JSONValue = JSONPrimitive | JSONObject | JSONArray; // @public export class MimeData { clear(): void; clearData(mime: string): void; getData(mime: string): any | undefined; hasData(mime: string): boolean; setData(mime: string, data: any): void; types(): string[]; } // @public export interface PartialJSONArray extends Array { } // @public export interface PartialJSONObject { // (undocumented) [key: string]: PartialJSONValue | undefined; } // @public export type PartialJSONValue = JSONPrimitive | PartialJSONObject | PartialJSONArray; // @public export class PromiseDelegate { constructor(); readonly promise: Promise; reject(reason: any): void; resolve(value: T | PromiseLike): void; } // @public export namespace Random { const getRandomValues: (buffer: Uint8Array) => void; } // @public export interface ReadonlyJSONArray extends ReadonlyArray { } // @public export interface ReadonlyJSONObject { // (undocumented) readonly [key: string]: ReadonlyJSONValue; } // @public export type ReadonlyJSONValue = JSONPrimitive | ReadonlyJSONObject | ReadonlyJSONArray; // @public export interface ReadonlyPartialJSONArray extends ReadonlyArray { } // @public export interface ReadonlyPartialJSONObject { // (undocumented) readonly [key: string]: ReadonlyPartialJSONValue | undefined; } // @public export type ReadonlyPartialJSONValue = JSONPrimitive | ReadonlyPartialJSONObject | ReadonlyPartialJSONArray; // @public export class Token { constructor(name: string); readonly name: string; } // @public export namespace UUID { const uuid4: () => string; } // (No @packageDocumentation comment for this package) ``` lumino-2021.12.13/review/api/datagrid.api.md000066400000000000000000000546421415564225700203570ustar00rootroot00000000000000## API Report File for "@lumino/datagrid" > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts import { IDisposable } from '@lumino/disposable'; import { IIterator } from '@lumino/algorithm'; import { IMessageHandler } from '@lumino/messaging'; import { ISignal } from '@lumino/signaling'; import { Message } from '@lumino/messaging'; import { ReadonlyJSONObject } from '@lumino/coreutils'; import { Widget } from '@lumino/widgets'; // @public export class BasicKeyHandler implements DataGrid.IKeyHandler { dispose(): void; readonly isDisposed: boolean; protected onArrowDown(grid: DataGrid, event: KeyboardEvent): void; protected onArrowLeft(grid: DataGrid, event: KeyboardEvent): void; protected onArrowRight(grid: DataGrid, event: KeyboardEvent): void; protected onArrowUp(grid: DataGrid, event: KeyboardEvent): void; protected onEscape(grid: DataGrid, event: KeyboardEvent): void; protected onKeyC(grid: DataGrid, event: KeyboardEvent): void; onKeyDown(grid: DataGrid, event: KeyboardEvent): void; protected onPageDown(grid: DataGrid, event: KeyboardEvent): void; protected onPageUp(grid: DataGrid, event: KeyboardEvent): void; } // @public export class BasicMouseHandler implements DataGrid.IMouseHandler { dispose(): void; readonly isDisposed: boolean; onContextMenu(grid: DataGrid, event: MouseEvent): void; onMouseDown(grid: DataGrid, event: MouseEvent): void; onMouseHover(grid: DataGrid, event: MouseEvent): void; onMouseLeave(grid: DataGrid, event: MouseEvent): void; onMouseMove(grid: DataGrid, event: MouseEvent): void; onMouseUp(grid: DataGrid, event: MouseEvent): void; onWheel(grid: DataGrid, event: WheelEvent): void; release(): void; } // @public export class BasicSelectionModel extends SelectionModel { clear(): void; currentSelection(): SelectionModel.Selection | null; readonly cursorColumn: number; readonly cursorRow: number; readonly isEmpty: boolean; protected onDataModelChanged(sender: DataModel, args: DataModel.ChangedArgs): void; select(args: SelectionModel.SelectArgs): void; selections(): IIterator; } // @public export abstract class CellRenderer { abstract paint(gc: GraphicsContext, config: CellRenderer.CellConfig): void; } // @public export namespace CellRenderer { export type CellConfig = { readonly x: number; readonly y: number; readonly height: number; readonly width: number; readonly region: DataModel.CellRegion; readonly row: number; readonly column: number; readonly value: any; readonly metadata: DataModel.Metadata; }; export type ConfigFunc = (config: CellConfig) => T; export type ConfigOption = T | ConfigFunc; export function resolveOption(option: ConfigOption, config: CellConfig): T; } // @public export class DataGrid extends Widget { constructor(options?: DataGrid.IOptions); readonly bodyHeight: number; readonly bodyWidth: number; cellRenderers: RendererMap; columnAt(region: DataModel.ColumnRegion, offset: number): number; columnCount(region: DataModel.RowRegion): number; columnOffset(region: DataModel.ColumnRegion, index: number): number; columnSize(region: DataModel.ColumnRegion, index: number): number; copyConfig: DataGrid.CopyConfig; copyToClipboard(): void; dataModel: DataModel | null; defaultSizes: DataGrid.DefaultSizes; dispose(): void; handleEvent(event: Event): void; readonly headerHeight: number; headerVisibility: DataGrid.HeaderVisibility; readonly headerWidth: number; hitTest(clientX: number, clientY: number): DataGrid.HitTestResult; keyHandler: DataGrid.IKeyHandler | null; mapToLocal(clientX: number, clientY: number): { lx: number; ly: number; }; mapToVirtual(clientX: number, clientY: number): { vx: number; vy: number; }; readonly maxScrollX: number; readonly maxScrollY: number; messageHook(handler: IMessageHandler, msg: Message): boolean; mouseHandler: DataGrid.IMouseHandler | null; protected onActivateRequest(msg: Message): void; protected onAfterDetach(msg: Message): void; protected onBeforeAttach(msg: Message): void; protected onBeforeShow(msg: Message): void; protected onResize(msg: Widget.ResizeMessage): void; readonly pageHeight: number; readonly pageWidth: number; processMessage(msg: Message): void; resetColumns(region: DataModel.ColumnRegion | 'all'): void; resetRows(region: DataModel.RowRegion | 'all'): void; resizeColumn(region: DataModel.ColumnRegion, index: number, size: number): void; resizeRow(region: DataModel.RowRegion, index: number, size: number): void; rowAt(region: DataModel.RowRegion, offset: number): number; rowCount(region: DataModel.RowRegion): number; rowOffset(region: DataModel.RowRegion, index: number): number; rowSize(region: DataModel.RowRegion, index: number): number; scrollBy(dx: number, dy: number): void; scrollByPage(dir: 'up' | 'down' | 'left' | 'right'): void; scrollByStep(dir: 'up' | 'down' | 'left' | 'right'): void; scrollTo(x: number, y: number): void; scrollToCell(row: number, column: number): void; scrollToColumn(column: number): void; scrollToCursor(): void; scrollToRow(row: number): void; readonly scrollX: number; readonly scrollY: number; selectionModel: SelectionModel | null; stretchLastColumn: boolean; stretchLastRow: boolean; style: DataGrid.Style; readonly totalHeight: number; readonly totalWidth: number; readonly viewport: Widget; readonly viewportHeight: number; readonly viewportWidth: number; } // @public export namespace DataGrid { export type CopyConfig = { readonly separator: string; readonly headers: 'none' | 'row' | 'column' | 'all'; readonly format: CopyFormatFunc; readonly warningThreshold: number; }; export type CopyFormatArgs = { region: DataModel.CellRegion; row: number; column: number; value: any; metadata: DataModel.Metadata; }; export type CopyFormatFunc = (args: CopyFormatArgs) => string; export function copyFormatGeneric(args: CopyFormatArgs): string; export type DefaultSizes = { readonly rowHeight: number; readonly columnWidth: number; readonly rowHeaderWidth: number; readonly columnHeaderHeight: number; }; export type HeaderVisibility = 'all' | 'row' | 'column' | 'none'; export type HitTestResult = { readonly region: DataModel.CellRegion | 'void'; readonly row: number; readonly column: number; readonly x: number; readonly y: number; readonly width: number; readonly height: number; }; export interface IKeyHandler extends IDisposable { onKeyDown(grid: DataGrid, event: KeyboardEvent): void; } export interface IMouseHandler extends IDisposable { onContextMenu(grid: DataGrid, event: MouseEvent): void; onMouseDown(grid: DataGrid, event: MouseEvent): void; onMouseHover(grid: DataGrid, event: MouseEvent): void; onMouseLeave(grid: DataGrid, event: MouseEvent): void; onMouseMove(grid: DataGrid, event: MouseEvent): void; onMouseUp(grid: DataGrid, event: MouseEvent): void; onWheel(grid: DataGrid, event: WheelEvent): void; release(): void; } export interface IOptions { cellRenderers?: RendererMap; copyConfig?: CopyConfig; defaultRenderer?: CellRenderer; defaultSizes?: DefaultSizes; headerVisibility?: HeaderVisibility; stretchLastColumn?: boolean; stretchLastRow?: boolean; style?: Style; } export type Style = { readonly voidColor?: string; readonly backgroundColor?: string; readonly rowBackgroundColor?: (index: number) => string; readonly columnBackgroundColor?: (index: number) => string; readonly gridLineColor?: string; readonly verticalGridLineColor?: string; readonly horizontalGridLineColor?: string; readonly headerBackgroundColor?: string; readonly headerGridLineColor?: string; readonly headerVerticalGridLineColor?: string; readonly headerHorizontalGridLineColor?: string; readonly selectionFillColor?: string; readonly selectionBorderColor?: string; readonly cursorFillColor?: string; readonly cursorBorderColor?: string; readonly headerSelectionFillColor?: string; readonly headerSelectionBorderColor?: string; readonly scrollShadow?: { readonly size: number; readonly color1: string; readonly color2: string; readonly color3: string; }; }; const defaultStyle: Style; const defaultSizes: DefaultSizes; const defaultCopyConfig: CopyConfig; } // @public export abstract class DataModel { readonly changed: ISignal; abstract columnCount(region: DataModel.ColumnRegion): number; abstract data(region: DataModel.CellRegion, row: number, column: number): any; protected emitChanged(args: DataModel.ChangedArgs): void; metadata(region: DataModel.CellRegion, row: number, column: number): DataModel.Metadata; abstract rowCount(region: DataModel.RowRegion): number; } // @public export namespace DataModel { export type CellRegion = 'body' | 'row-header' | 'column-header' | 'corner-header'; export type CellsChangedArgs = { readonly type: 'cells-changed'; readonly region: CellRegion; readonly row: number; readonly column: number; readonly rowSpan: number; readonly columnSpan: number; }; export type ChangedArgs = (RowsChangedArgs | ColumnsChangedArgs | RowsMovedArgs | ColumnsMovedArgs | CellsChangedArgs | ModelResetArgs); export type ColumnRegion = 'body' | 'row-header'; const emptyMetadata: Metadata; export type ColumnsChangedArgs = { readonly type: 'columns-inserted' | 'columns-removed'; readonly region: ColumnRegion; readonly index: number; readonly span: number; }; export type ColumnsMovedArgs = { readonly type: 'columns-moved'; readonly region: ColumnRegion; readonly index: number; readonly span: number; readonly destination: number; }; export type Metadata = { [key: string]: any; }; export type ModelResetArgs = { readonly type: 'model-reset'; }; export type RowRegion = 'body' | 'column-header'; export type RowsChangedArgs = { readonly type: 'rows-inserted' | 'rows-removed'; readonly region: RowRegion; readonly index: number; readonly span: number; }; export type RowsMovedArgs = { readonly type: 'rows-moved'; readonly region: RowRegion; readonly index: number; readonly span: number; readonly destination: number; }; } // @public export class GraphicsContext implements IDisposable { constructor(context: CanvasRenderingContext2D); // (undocumented) arc(x: number, y: number, radius: number, startAngle: number, endAngle: number, anticlockwise?: boolean): void; // (undocumented) arcTo(x1: number, y1: number, x2: number, y2: number, radius: number): void; // (undocumented) beginPath(): void; // (undocumented) bezierCurveTo(cp1x: number, cp1y: number, cp2x: number, cp2y: number, x: number, y: number): void; // (undocumented) clearRect(x: number, y: number, w: number, h: number): void; // (undocumented) clip(fillRule?: CanvasFillRule): void; // (undocumented) closePath(): void; // (undocumented) createImageData(imageData: ImageData): ImageData; // (undocumented) createImageData(sw: number, sh: number): ImageData; // (undocumented) createLinearGradient(x0: number, y0: number, x1: number, y1: number): CanvasGradient; // (undocumented) createPattern(image: HTMLImageElement | HTMLCanvasElement | HTMLVideoElement, repetition: string): CanvasPattern; // (undocumented) createRadialGradient(x0: number, y0: number, r0: number, x1: number, y1: number, r1: number): CanvasGradient; // (undocumented) dispose(): void; // (undocumented) drawFocusIfNeeded(element: Element): void; // (undocumented) drawImage(image: HTMLImageElement | HTMLCanvasElement | HTMLVideoElement | ImageBitmap, dstX: number, dstY: number): void; // (undocumented) drawImage(image: HTMLImageElement | HTMLCanvasElement | HTMLVideoElement | ImageBitmap, dstX: number, dstY: number, dstW: number, dstH: number): void; // (undocumented) drawImage(image: HTMLImageElement | HTMLCanvasElement | HTMLVideoElement | ImageBitmap, srcX: number, srcY: number, srcW: number, srcH: number, dstX: number, dstY: number, dstW: number, dstH: number): void; // (undocumented) ellipse(x: number, y: number, radiusX: number, radiusY: number, rotation: number, startAngle: number, endAngle: number, anticlockwise?: boolean): void; // (undocumented) fill(fillRule?: CanvasFillRule): void; // (undocumented) fillRect(x: number, y: number, w: number, h: number): void; // (undocumented) fillStyle: string | CanvasGradient | CanvasPattern; // (undocumented) fillText(text: string, x: number, y: number, maxWidth?: number): void; // (undocumented) font: string; // (undocumented) getImageData(sx: number, sy: number, sw: number, sh: number): ImageData; // (undocumented) getLineDash(): number[]; // (undocumented) globalAlpha: number; // (undocumented) globalCompositeOperation: string; // (undocumented) imageSmoothingEnabled: boolean; // (undocumented) readonly isDisposed: boolean; // (undocumented) isPointInPath(x: number, y: number, fillRule?: CanvasFillRule): boolean; // (undocumented) lineCap: string; // (undocumented) lineDashOffset: number; // (undocumented) lineJoin: string; // (undocumented) lineTo(x: number, y: number): void; // (undocumented) lineWidth: number; // (undocumented) measureText(text: string): TextMetrics; // (undocumented) miterLimit: number; // (undocumented) moveTo(x: number, y: number): void; // (undocumented) putImageData(imagedata: ImageData, dx: number, dy: number): void; // (undocumented) putImageData(imagedata: ImageData, dx: number, dy: number, dirtyX: number, dirtyY: number, dirtyWidth: number, dirtyHeight: number): void; // (undocumented) quadraticCurveTo(cpx: number, cpy: number, x: number, y: number): void; // (undocumented) rect(x: number, y: number, w: number, h: number): void; // (undocumented) restore(): void; // (undocumented) rotate(angle: number): void; // (undocumented) save(): void; // (undocumented) scale(x: number, y: number): void; // (undocumented) setLineDash(segments: number[]): void; // (undocumented) setTransform(m11: number, m12: number, m21: number, m22: number, dx: number, dy: number): void; // (undocumented) shadowBlur: number; // (undocumented) shadowColor: string; // (undocumented) shadowOffsetX: number; // (undocumented) shadowOffsetY: number; // (undocumented) stroke(): void; // (undocumented) strokeRect(x: number, y: number, w: number, h: number): void; // (undocumented) strokeStyle: string | CanvasGradient | CanvasPattern; // (undocumented) strokeText(text: string, x: number, y: number, maxWidth?: number): void; // (undocumented) textAlign: string; // (undocumented) textBaseline: string; // (undocumented) transform(m11: number, m12: number, m21: number, m22: number, dx: number, dy: number): void; // (undocumented) translate(x: number, y: number): void; } // @public export class JSONModel extends DataModel { constructor(options: JSONModel.IOptions); columnCount(region: DataModel.ColumnRegion): number; data(region: DataModel.CellRegion, row: number, column: number): any; metadata(region: DataModel.CellRegion, row: number, column: number): DataModel.Metadata; rowCount(region: DataModel.RowRegion): number; } // @public export namespace JSONModel { export type DataSource = ReadonlyArray; export type Field = { readonly name: string; readonly type?: string; readonly format?: string; readonly title?: string; }; export interface IOptions { data: DataSource; schema: Schema; } export type Schema = { readonly fields: Field[]; readonly missingValues?: string[]; readonly primaryKey?: string | string[]; }; } // @public export class RendererMap { constructor(values?: RendererMap.Values, fallback?: CellRenderer); readonly changed: ISignal; get(config: CellRenderer.CellConfig): CellRenderer; update(values?: RendererMap.Values, fallback?: CellRenderer): void; } // @public export namespace RendererMap { export type Resolver = CellRenderer.ConfigFunc; export type Values = { [R in DataModel.CellRegion]?: Resolver | CellRenderer | undefined; }; } // @public export abstract class SelectionModel { constructor(options: SelectionModel.IOptions); readonly changed: ISignal; abstract clear(): void; abstract currentSelection(): SelectionModel.Selection | null; abstract readonly cursorColumn: number; abstract readonly cursorRow: number; readonly dataModel: DataModel; protected emitChanged(): void; isCellSelected(row: number, column: number): boolean; isColumnSelected(index: number): boolean; abstract readonly isEmpty: boolean; isRowSelected(index: number): boolean; protected onDataModelChanged(sender: DataModel, args: DataModel.ChangedArgs): void; abstract select(args: SelectionModel.SelectArgs): void; selectionMode: SelectionModel.SelectionMode; abstract selections(): IIterator; } // @public export namespace SelectionModel { export type ClearMode = 'all' | 'current' | 'none'; export interface IOptions { dataModel: DataModel; selectionMode?: SelectionMode; } export type SelectArgs = { r1: number; c1: number; r2: number; c2: number; cursorRow: number; cursorColumn: number; clear: ClearMode; }; export type Selection = { readonly r1: number; readonly c1: number; readonly r2: number; readonly c2: number; }; export type SelectionMode = 'row' | 'column' | 'cell'; } // @public export class TextRenderer extends CellRenderer { constructor(options?: TextRenderer.IOptions); readonly backgroundColor: CellRenderer.ConfigOption; drawBackground(gc: GraphicsContext, config: CellRenderer.CellConfig): void; drawText(gc: GraphicsContext, config: CellRenderer.CellConfig): void; readonly font: CellRenderer.ConfigOption; readonly format: TextRenderer.FormatFunc; readonly horizontalAlignment: CellRenderer.ConfigOption; paint(gc: GraphicsContext, config: CellRenderer.CellConfig): void; readonly textColor: CellRenderer.ConfigOption; readonly verticalAlignment: CellRenderer.ConfigOption; } // @public export namespace TextRenderer { export function formatDate(options?: formatDate.IOptions): FormatFunc; export namespace formatDate { export interface IOptions { missing?: string; } } export function formatExponential(options?: formatExponential.IOptions): FormatFunc; export namespace formatExponential { export interface IOptions { digits?: number; missing?: string; } } export function formatFixed(options?: formatFixed.IOptions): FormatFunc; export namespace formatFixed { export interface IOptions { digits?: number; missing?: string; } } export type FormatFunc = CellRenderer.ConfigFunc; export function formatGeneric(options?: formatGeneric.IOptions): FormatFunc; export namespace formatGeneric { export interface IOptions { missing?: string; } } export function formatIntlDateTime(options?: formatIntlDateTime.IOptions): FormatFunc; export namespace formatIntlDateTime { export interface IOptions { locales?: string | string[]; missing?: string; options?: Intl.DateTimeFormatOptions; } } export function formatIntlNumber(options?: formatIntlNumber.IOptions): FormatFunc; export namespace formatIntlNumber { export interface IOptions { locales?: string | string[]; missing?: string; options?: Intl.NumberFormatOptions; } } export function formatISODateTime(options?: formatISODateTime.IOptions): FormatFunc; export namespace formatISODateTime { export interface IOptions { missing?: string; } } export function formatPrecision(options?: formatPrecision.IOptions): FormatFunc; export namespace formatPrecision { export interface IOptions { digits?: number; missing?: string; } } export function formatTime(options?: formatTime.IOptions): FormatFunc; export namespace formatTime { export interface IOptions { missing?: string; } } export function formatUTCDateTime(options?: formatUTCDateTime.IOptions): FormatFunc; export namespace formatUTCDateTime { export interface IOptions { missing?: string; } } export type HorizontalAlignment = 'left' | 'center' | 'right'; export interface IOptions { backgroundColor?: CellRenderer.ConfigOption; font?: CellRenderer.ConfigOption; format?: FormatFunc; horizontalAlignment?: CellRenderer.ConfigOption; textColor?: CellRenderer.ConfigOption; verticalAlignment?: CellRenderer.ConfigOption; } export function measureFontHeight(font: string): number; export type VerticalAlignment = 'top' | 'center' | 'bottom'; } // (No @packageDocumentation comment for this package) ``` lumino-2021.12.13/review/api/datastore.api.md000066400000000000000000000440751415564225700205650ustar00rootroot00000000000000## API Report File for "@lumino/datastore" > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts import { IDisposable } from '@lumino/disposable'; import { IIterable } from '@lumino/algorithm'; import { IIterator } from '@lumino/algorithm'; import { IMessageHandler } from '@lumino/messaging'; import { ISignal } from '@lumino/signaling'; import { IterableOrArrayLike } from '@lumino/algorithm'; import { Message } from '@lumino/messaging'; import { ReadonlyJSONValue } from '@lumino/coreutils'; // @public export type AnyField = Field; // @public export class Datastore implements IDisposable, IIterable>, IMessageHandler { readonly adapter: IServerAdapter | null; beginTransaction(): string; readonly changed: ISignal; static create(options: Datastore.IOptions): Datastore; dispose(): void; endTransaction(): void; get(schema: S): Table; readonly id: number; readonly inTransaction: boolean; readonly isDisposed: boolean; iter(): IIterator>; processMessage(msg: Message): void; redo(transactionId: string): Promise; toString(): string; undo(transactionId: string): Promise; readonly version: number; } // @public export namespace Datastore { export type Change = { readonly [schemaId: string]: Table.Change; }; // Warning: (ae-forgotten-export) The symbol "Private" needs to be exported by the entry point index.d.ts // // @internal (undocumented) export type Context = Readonly; export type DataLocation = { datastore: Datastore; }; export type FieldLocation = RecordLocation & { field: F; }; export function getField(datastore: Datastore, loc: FieldLocation): S['fields'][F]['ValueType']; export function getRecord(datastore: Datastore, loc: RecordLocation): Record.Value | undefined; export function getTable(datastore: Datastore, loc: TableLocation): Table; export interface IChangedArgs { readonly change: Change; readonly storeId: number; readonly transactionId: string; readonly type: TransactionType; } export interface IOptions { adapter?: IServerAdapter; id: number; restoreState?: string; schemas: ReadonlyArray; transactionIdFactory?: TransactionIdFactory; } export function listenField(datastore: Datastore, loc: FieldLocation, slot: (source: Datastore, args: S['fields'][F]['ChangeType']) => void, thisArg?: any): IDisposable; export function listenRecord(datastore: Datastore, loc: RecordLocation, slot: (source: Datastore, args: Record.Change) => void, thisArg?: any): IDisposable; export function listenTable(datastore: Datastore, loc: TableLocation, slot: (source: Datastore, args: Table.Change) => void, thisArg?: any): IDisposable; // @internal (undocumented) export type MutableChange = { [schemaId: string]: Table.MutableChange; }; // @internal (undocumented) export type MutablePatch = { [schemaId: string]: Table.MutablePatch; }; export type Patch = { readonly [schemaId: string]: Table.Patch; }; export type RecordLocation = TableLocation & { record: string; }; export type TableLocation = { schema: S; }; export type Transaction = { readonly id: string; readonly storeId: number; readonly patch: Patch; readonly version: number; }; export type TransactionIdFactory = (version: number, storeId: number) => string; export type TransactionType = 'transaction' | 'undo' | 'redo'; export function updateField(datastore: Datastore, loc: FieldLocation, update: S['fields'][F]['UpdateType']): void; export function updateRecord(datastore: Datastore, loc: RecordLocation, update: Record.Update): void; export function updateTable(datastore: Datastore, loc: TableLocation, update: Table.Update): void; export function withTransaction(datastore: Datastore, update: (id: string) => void): string; } // @public export abstract class Field { constructor(options?: Field.IOptions); abstract applyPatch(args: Field.PatchArgs): Field.PatchResult; abstract applyUpdate(args: Field.UpdateArgs): Field.UpdateResult; readonly ChangeType: Change; abstract createMetadata(): Metadata; abstract createValue(): Value; readonly description: string; abstract mergeChange(first: Change, second: Change): Change; abstract mergePatch(first: Patch, second: Patch): Patch; readonly MetadataType: Metadata; readonly PatchType: Patch; abstract readonly type: string; abstract unapplyPatch(args: Field.PatchArgs): Field.PatchResult; readonly UpdateType: Update; readonly ValueType: Value; } // @public export namespace Field { export interface IOptions { description?: string; } export type PatchArgs = { readonly previous: Value; readonly patch: Patch; readonly metadata: Metadata; }; export type PatchResult = { readonly value: Value; readonly change: Change; }; export type UpdateArgs = { readonly previous: Value; readonly update: Update; readonly metadata: Metadata; readonly version: number; readonly storeId: number; }; export type UpdateResult = { readonly value: Value; readonly change: Change; readonly patch: Patch; }; } // @public export namespace Fields { export function Boolean(options?: Partial>): RegisterField; export function List(options?: ListField.IOptions): ListField; export function Map(options?: MapField.IOptions): MapField; export function Number(options?: Partial>): RegisterField; export function Register(options: RegisterField.IOptions): RegisterField; export function String(options?: Partial>): RegisterField; export function Text(options?: TextField.IOptions): TextField; } // @public export interface IServerAdapter extends IDisposable { broadcast(transaction: Datastore.Transaction): void; onRedo: ((transaction: Datastore.Transaction) => void) | null; onRemoteTransaction: ((transaction: Datastore.Transaction) => void) | null; onUndo: ((transaction: Datastore.Transaction) => void) | null; redo(id: string): Promise; undo(id: string): Promise; } // @public export class ListField extends Field, ListField.Update, ListField.Metadata, ListField.Change, ListField.Patch> { constructor(options?: ListField.IOptions); applyPatch(args: Field.PatchArgs, ListField.Patch, ListField.Metadata>): Field.PatchResult, ListField.Change>; applyUpdate(args: Field.UpdateArgs, ListField.Update, ListField.Metadata>): Field.UpdateResult, ListField.Change, ListField.Patch>; createMetadata(): ListField.Metadata; createValue(): ListField.Value; mergeChange(first: ListField.Change, second: ListField.Change): ListField.Change; mergePatch(first: ListField.Patch, second: ListField.Patch): ListField.Patch; readonly type: 'list'; unapplyPatch(args: Field.PatchArgs, ListField.Patch, ListField.Metadata>): Field.PatchResult, ListField.Change>; } // @public export namespace ListField { export type Change = ReadonlyArray>; export type ChangePart = { readonly index: number; readonly removed: ReadonlyArray; readonly inserted: ReadonlyArray; }; export interface IOptions extends Field.IOptions { } export type Metadata = { readonly ids: Array; readonly cemetery: { [id: string]: number; }; }; export type Patch = ReadonlyArray>; export type PatchPart = { readonly removedIds: ReadonlyArray; readonly removedValues: ReadonlyArray; readonly insertedIds: ReadonlyArray; readonly insertedValues: ReadonlyArray; }; export type Splice = { readonly index: number; readonly remove: number; readonly values: ReadonlyArray; }; export type Update = Splice | ReadonlyArray>; export type Value = ReadonlyArray; } // @public export class MapField extends Field, MapField.Update, MapField.Metadata, MapField.Change, MapField.Patch> { constructor(options?: MapField.IOptions); applyPatch(args: Field.PatchArgs, MapField.Patch, MapField.Metadata>): Field.PatchResult, MapField.Change>; applyUpdate(args: Field.UpdateArgs, MapField.Update, MapField.Metadata>): Field.UpdateResult, MapField.Change, MapField.Patch>; createMetadata(): MapField.Metadata; createValue(): MapField.Value; mergeChange(first: MapField.Change, second: MapField.Change): MapField.Change; mergePatch(first: MapField.Patch, second: MapField.Patch): MapField.Patch; readonly type: 'map'; unapplyPatch(args: Field.PatchArgs, MapField.Patch, MapField.Metadata>): Field.PatchResult, MapField.Change>; } // @public export namespace MapField { export type Change = { readonly previous: { readonly [key: string]: T | null; }; readonly current: { readonly [key: string]: T | null; }; }; export interface IOptions extends Field.IOptions { } export type Metadata = { readonly ids: { [key: string]: Array; }; readonly values: { [key: string]: Array; }; }; export type Patch = { readonly id: string; readonly values: { readonly [key: string]: T | null; }; }; export type Update = { readonly [key: string]: T | null; }; export type Value = { readonly [key: string]: T; }; } // @public export type Record = Record.Base & Record.Value; // @public export namespace Record { export type Base = { readonly $id: string; readonly '@@metadata': Metadata; }; export type Change = { readonly [N in keyof S['fields']]?: S['fields'][N]['ChangeType']; }; // @internal export type Metadata = { readonly [N in keyof S['fields']]: S['fields'][N]['MetadataType']; }; // @internal export type MutableChange = { [N in keyof S['fields']]?: S['fields'][N]['ChangeType']; }; // @internal export type MutablePatch = { [N in keyof S['fields']]?: S['fields'][N]['PatchType']; }; export type Patch = { readonly [N in keyof S['fields']]?: S['fields'][N]['PatchType']; }; export type Update = { readonly [N in keyof S['fields']]?: S['fields'][N]['UpdateType']; }; export type Value = { readonly [N in keyof S['fields']]: S['fields'][N]['ValueType']; }; } // @public export class RegisterField extends Field, RegisterField.Update, RegisterField.Metadata, RegisterField.Change, RegisterField.Patch> { constructor(options: RegisterField.IOptions); applyPatch(args: Field.PatchArgs, RegisterField.Patch, RegisterField.Metadata>): Field.PatchResult, RegisterField.Change>; applyUpdate(args: Field.UpdateArgs, RegisterField.Update, RegisterField.Metadata>): Field.UpdateResult, RegisterField.Change, RegisterField.Patch>; createMetadata(): RegisterField.Metadata; createValue(): RegisterField.Value; mergeChange(first: RegisterField.Change, second: RegisterField.Change): RegisterField.Change; mergePatch(first: RegisterField.Patch, second: RegisterField.Patch): RegisterField.Patch; readonly type: 'register'; unapplyPatch(args: Field.PatchArgs, RegisterField.Patch, RegisterField.Metadata>): Field.PatchResult, RegisterField.Change>; readonly value: T; } // @public export namespace RegisterField { export type Change = { readonly previous: T; readonly current: T; }; export interface IOptions extends Field.IOptions { value: T; } export type Metadata = { readonly ids: Array; readonly values: Array; }; export type Patch = { readonly id: string; readonly value: T; }; export type Update = T; export type Value = T; } // @public export type Schema = { readonly id: string; readonly fields: { readonly [name: string]: AnyField; }; }; // @public export class Table implements IIterable> { // @internal static create(schema: U, context: Datastore.Context): Table; get(id: string): Record | undefined; has(id: string): boolean; readonly isEmpty: boolean; iter(): IIterator>; // @internal static patch(table: Table, data: Table.Patch): Table.Change; // @internal static recreate(schema: U, context: Datastore.Context, records: IterableOrArrayLike>): Table; readonly schema: S; readonly size: number; // @internal static unpatch(table: Table, data: Table.Patch): Table.Change; update(data: Table.Update): void; } // @public export namespace Table { export type Change = { readonly [recordId: string]: Record.Change; }; // @internal export type MutableChange = { [recordId: string]: Record.MutableChange; }; // @internal export type MutablePatch = { [recordId: string]: Record.MutablePatch; }; export type Patch = { readonly [recordId: string]: Record.Patch; }; export type Update = { readonly [recordId: string]: Record.Update; }; } // @public export class TextField extends Field { constructor(options?: TextField.IOptions); applyPatch(args: Field.PatchArgs): Field.PatchResult; applyUpdate(args: Field.UpdateArgs): Field.UpdateResult; createMetadata(): TextField.Metadata; createValue(): TextField.Value; mergeChange(first: TextField.Change, second: TextField.Change): TextField.Change; mergePatch(first: TextField.Patch, second: TextField.Patch): TextField.Patch; readonly type: 'text'; unapplyPatch(args: Field.PatchArgs): Field.PatchResult; } // @public export namespace TextField { export type Change = ReadonlyArray; export type ChangePart = { readonly index: number; readonly removed: string; readonly inserted: string; }; export interface IOptions extends Field.IOptions { } export type Metadata = { readonly ids: Array; readonly cemetery: { [id: string]: number; }; }; export type Patch = ReadonlyArray; export type PatchPart = { readonly removedIds: ReadonlyArray; readonly removedText: string; readonly insertedIds: ReadonlyArray; readonly insertedText: string; }; export type Splice = { readonly index: number; readonly remove: number; readonly text: string; }; export type Update = Splice | ReadonlyArray; export type Value = string; } // @public export function validateSchema(schema: Schema): string[]; // (No @packageDocumentation comment for this package) ``` lumino-2021.12.13/review/api/disposable.api.md000066400000000000000000000031071415564225700207130ustar00rootroot00000000000000## API Report File for "@lumino/disposable" > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts import { ISignal } from '@lumino/signaling'; import { IterableOrArrayLike } from '@lumino/algorithm'; // @public export class DisposableDelegate implements IDisposable { constructor(fn: () => void); dispose(): void; readonly isDisposed: boolean; } // @public export class DisposableSet implements IDisposable { constructor(); add(item: IDisposable): void; clear(): void; contains(item: IDisposable): boolean; dispose(): void; readonly isDisposed: boolean; remove(item: IDisposable): void; } // @public export namespace DisposableSet { export function from(items: IterableOrArrayLike): DisposableSet; } // @public export interface IDisposable { dispose(): void; readonly isDisposed: boolean; } // @public export interface IObservableDisposable extends IDisposable { readonly disposed: ISignal; } // @public export class ObservableDisposableDelegate extends DisposableDelegate implements IObservableDisposable { dispose(): void; readonly disposed: ISignal; } // @public export class ObservableDisposableSet extends DisposableSet implements IObservableDisposable { dispose(): void; readonly disposed: ISignal; } // @public export namespace ObservableDisposableSet { export function from(items: IterableOrArrayLike): ObservableDisposableSet; } // (No @packageDocumentation comment for this package) ``` lumino-2021.12.13/review/api/domutils.api.md000066400000000000000000000030661415564225700204320ustar00rootroot00000000000000## API Report File for "@lumino/domutils" > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts // @public export namespace ClipboardExt { export function copyText(text: string): void; } // @public export namespace ElementExt { export function boxSizing(element: Element): IBoxSizing; export function hitTest(element: Element, clientX: number, clientY: number): boolean; export interface IBoxSizing { borderBottom: number; borderLeft: number; borderRight: number; borderTop: number; horizontalSum: number; paddingBottom: number; paddingLeft: number; paddingRight: number; paddingTop: number; verticalSum: number; } export interface ISizeLimits { maxHeight: number; maxWidth: number; minHeight: number; minWidth: number; } export function scrollIntoViewIfNeeded(area: Element, element: Element): void; export function sizeLimits(element: Element): ISizeLimits; } // @public export namespace Platform { const IS_MAC: boolean; const IS_WIN: boolean; const IS_IE: boolean; const IS_EDGE: boolean; export function accelKey(event: KeyboardEvent | MouseEvent): boolean; } // @public export namespace Selector { export function calculateSpecificity(selector: string): number; export function isValid(selector: string): boolean; export function matches(element: Element, selector: string): boolean; } // (No @packageDocumentation comment for this package) ``` lumino-2021.12.13/review/api/dragdrop.api.md000066400000000000000000000027131415564225700203720ustar00rootroot00000000000000## API Report File for "@lumino/dragdrop" > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts import { IDisposable } from '@lumino/disposable'; import { MimeData } from '@lumino/coreutils'; // @public export class Drag implements IDisposable { constructor(options: Drag.IOptions); dispose(): void; readonly dragImage: HTMLElement | null; handleEvent(event: Event): void; readonly isDisposed: boolean; readonly mimeData: MimeData; readonly proposedAction: DropAction; readonly source: any; start(clientX: number, clientY: number): Promise; readonly supportedActions: SupportedActions; } // @public export namespace Drag { export interface IOptions { dragImage?: HTMLElement; mimeData: MimeData; proposedAction?: DropAction; source?: any; supportedActions?: SupportedActions; } export function overrideCursor(cursor: string): IDisposable; } // @public export type DropAction = 'none' | 'copy' | 'link' | 'move'; // @public export interface IDragEvent extends MouseEvent { dropAction: DropAction; readonly mimeData: MimeData; readonly proposedAction: DropAction; readonly source: any; readonly supportedActions: SupportedActions; } // @public export type SupportedActions = DropAction | 'copy-link' | 'copy-move' | 'link-move' | 'all'; // (No @packageDocumentation comment for this package) ``` lumino-2021.12.13/review/api/keyboard.api.md000066400000000000000000000021401415564225700203620ustar00rootroot00000000000000## API Report File for "@lumino/keyboard" > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts // @public export const EN_US: IKeyboardLayout; // @public export function getKeyboardLayout(): IKeyboardLayout; // @public export interface IKeyboardLayout { isValidKey(key: string): boolean; keyForKeydownEvent(event: KeyboardEvent): string; keys(): string[]; readonly name: string; } // @public export class KeycodeLayout implements IKeyboardLayout { constructor(name: string, codes: KeycodeLayout.CodeMap); isValidKey(key: string): boolean; keyForKeydownEvent(event: KeyboardEvent): string; keys(): string[]; readonly name: string; } // @public export namespace KeycodeLayout { export type CodeMap = { readonly [code: number]: string; }; export function extractKeys(codes: CodeMap): KeySet; export type KeySet = { readonly [key: string]: boolean; }; } // @public export function setKeyboardLayout(layout: IKeyboardLayout): void; // (No @packageDocumentation comment for this package) ``` lumino-2021.12.13/review/api/messaging.api.md000066400000000000000000000027701415564225700205500ustar00rootroot00000000000000## API Report File for "@lumino/messaging" > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts // @public export class ConflatableMessage extends Message { conflate(other: ConflatableMessage): boolean; readonly isConflatable: boolean; } // @public export interface IMessageHandler { processMessage(msg: Message): void; } // @public export interface IMessageHook { messageHook(handler: IMessageHandler, msg: Message): boolean; } // @public export class Message { constructor(type: string); conflate(other: Message): boolean; readonly isConflatable: boolean; readonly type: string; } // @public export type MessageHook = IMessageHook | ((handler: IMessageHandler, msg: Message) => boolean); // @public export namespace MessageLoop { export function clearData(handler: IMessageHandler): void; export type ExceptionHandler = (err: Error) => void; export function flush(): void; export function getExceptionHandler(): ExceptionHandler; export function installMessageHook(handler: IMessageHandler, hook: MessageHook): void; export function postMessage(handler: IMessageHandler, msg: Message): void; export function removeMessageHook(handler: IMessageHandler, hook: MessageHook): void; export function sendMessage(handler: IMessageHandler, msg: Message): void; export function setExceptionHandler(handler: ExceptionHandler): ExceptionHandler; } // (No @packageDocumentation comment for this package) ``` lumino-2021.12.13/review/api/polling.api.md000066400000000000000000000064541415564225700202420ustar00rootroot00000000000000## API Report File for "@lumino/polling" > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts import { IDisposable } from '@lumino/disposable'; import { IObservableDisposable } from '@lumino/disposable'; import { ISignal } from '@lumino/signaling'; import { PromiseDelegate } from '@lumino/coreutils'; // @public export class Debouncer extends RateLimiter { invoke(): Promise; } // @public export interface IPoll { readonly disposed: ISignal; readonly frequency: IPoll.Frequency; readonly isDisposed: boolean; readonly name: string; readonly state: IPoll.State; readonly tick: Promise>; readonly ticked: ISignal, IPoll.State>; } // @public export namespace IPoll { export type Frequency = { readonly backoff: boolean | number; readonly interval: number; readonly max: number; }; export type Phase = T | 'constructed' | 'disposed' | 'reconnected' | 'refreshed' | 'rejected' | 'resolved' | 'standby' | 'started' | 'stopped'; export type State = { readonly interval: number; readonly payload: T | U | null; readonly phase: Phase; readonly timestamp: number; }; } // @public export interface IRateLimiter extends IDisposable { invoke(): Promise; readonly limit: number; stop(): Promise; } // @public export class Poll implements IObservableDisposable, IPoll { constructor(options: Poll.IOptions); dispose(): void; readonly disposed: ISignal; frequency: IPoll.Frequency; readonly isDisposed: boolean; readonly name: string; refresh(): Promise; schedule(next?: Partial & { cancel: (last: IPoll.State) => boolean; }>): Promise; standby: Poll.Standby | (() => boolean | Poll.Standby); start(): Promise; readonly state: IPoll.State; stop(): Promise; readonly tick: Promise; readonly ticked: ISignal>; } // @public export namespace Poll { export type Factory = (state: IPoll.State) => Promise; export interface IOptions { auto?: boolean; factory: Factory; frequency?: Partial; name?: string; standby?: Standby | (() => boolean | Standby); } export type Standby = 'never' | 'when-hidden'; const IMMEDIATE = 0; const MAX_INTERVAL = 2147483647; const NEVER: number; } // @public export abstract class RateLimiter implements IRateLimiter { constructor(fn: () => T | Promise, limit?: number); dispose(): void; abstract invoke(): Promise; readonly isDisposed: boolean; readonly limit: number; protected payload: PromiseDelegate | null; protected poll: Poll; stop(): Promise; } // @public export class Throttler extends RateLimiter { invoke(): Promise; } // (No @packageDocumentation comment for this package) ``` lumino-2021.12.13/review/api/properties.api.md000066400000000000000000000014361415564225700207650ustar00rootroot00000000000000## API Report File for "@lumino/properties" > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts // @public export class AttachedProperty { constructor(options: AttachedProperty.IOptions); coerce(owner: T): void; get(owner: T): U; readonly name: string; set(owner: T, value: U): void; } // @public export namespace AttachedProperty { export function clearData(owner: any): void; export interface IOptions { changed?: (owner: T, oldValue: U, newValue: U) => void; coerce?: (owner: T, value: U) => U; compare?: (oldValue: U, newValue: U) => boolean; create: (owner: T) => U; name: string; } } // (No @packageDocumentation comment for this package) ``` lumino-2021.12.13/review/api/signaling.api.md000066400000000000000000000023221415564225700205370ustar00rootroot00000000000000## API Report File for "@lumino/signaling" > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts // @public export interface ISignal { connect(slot: Slot, thisArg?: any): boolean; disconnect(slot: Slot, thisArg?: any): boolean; } // @public export class Signal implements ISignal { constructor(sender: T); connect(slot: Slot, thisArg?: any): boolean; disconnect(slot: Slot, thisArg?: any): boolean; emit(args: U): void; readonly sender: T; } // @public export namespace Signal { export function clearData(object: any): void; export function disconnectAll(object: any): void; export function disconnectBetween(sender: any, receiver: any): void; export function disconnectReceiver(receiver: any): void; export function disconnectSender(sender: any): void; export type ExceptionHandler = (err: Error) => void; export function getExceptionHandler(): ExceptionHandler; export function setExceptionHandler(handler: ExceptionHandler): ExceptionHandler; } // @public export type Slot = (sender: T, args: U) => void; // (No @packageDocumentation comment for this package) ``` lumino-2021.12.13/review/api/virtualdom.api.md000066400000000000000000000375041415564225700207640ustar00rootroot00000000000000## API Report File for "@lumino/virtualdom" > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts // @public export type CSSPropertyNames = ('alignContent' | 'alignItems' | 'alignSelf' | 'alignmentBaseline' | 'animation' | 'animationDelay' | 'animationDirection' | 'animationDuration' | 'animationFillMode' | 'animationIterationCount' | 'animationName' | 'animationPlayState' | 'animationTimingFunction' | 'backfaceVisibility' | 'background' | 'backgroundAttachment' | 'backgroundClip' | 'backgroundColor' | 'backgroundImage' | 'backgroundOrigin' | 'backgroundPosition' | 'backgroundPositionX' | 'backgroundPositionY' | 'backgroundRepeat' | 'backgroundSize' | 'baselineShift' | 'border' | 'borderBottom' | 'borderBottomColor' | 'borderBottomLeftRadius' | 'borderBottomRightRadius' | 'borderBottomStyle' | 'borderBottomWidth' | 'borderCollapse' | 'borderColor' | 'borderImage' | 'borderImageOutset' | 'borderImageRepeat' | 'borderImageSlice' | 'borderImageSource' | 'borderImageWidth' | 'borderLeft' | 'borderLeftColor' | 'borderLeftStyle' | 'borderLeftWidth' | 'borderRadius' | 'borderRight' | 'borderRightColor' | 'borderRightStyle' | 'borderRightWidth' | 'borderSpacing' | 'borderStyle' | 'borderTop' | 'borderTopColor' | 'borderTopLeftRadius' | 'borderTopRightRadius' | 'borderTopStyle' | 'borderTopWidth' | 'borderWidth' | 'bottom' | 'boxShadow' | 'boxSizing' | 'breakAfter' | 'breakBefore' | 'breakInside' | 'captionSide' | 'clear' | 'clip' | 'clipPath' | 'clipRule' | 'color' | 'colorInterpolationFilters' | 'columnCount' | 'columnFill' | 'columnGap' | 'columnRule' | 'columnRuleColor' | 'columnRuleStyle' | 'columnRuleWidth' | 'columnSpan' | 'columnWidth' | 'columns' | 'content' | 'counterIncrement' | 'counterReset' | 'cssFloat' | 'cssText' | 'cursor' | 'direction' | 'display' | 'dominantBaseline' | 'emptyCells' | 'enableBackground' | 'fill' | 'fillOpacity' | 'fillRule' | 'filter' | 'flex' | 'flexBasis' | 'flexDirection' | 'flexFlow' | 'flexGrow' | 'flexShrink' | 'flexWrap' | 'floodColor' | 'floodOpacity' | 'font' | 'fontFamily' | 'fontFeatureSettings' | 'fontSize' | 'fontSizeAdjust' | 'fontStretch' | 'fontStyle' | 'fontVariant' | 'fontWeight' | 'glyphOrientationHorizontal' | 'glyphOrientationVertical' | 'height' | 'imeMode' | 'justifyContent' | 'kerning' | 'left' | 'letterSpacing' | 'lightingColor' | 'lineHeight' | 'listStyle' | 'listStyleImage' | 'listStylePosition' | 'listStyleType' | 'margin' | 'marginBottom' | 'marginLeft' | 'marginRight' | 'marginTop' | 'marker' | 'markerEnd' | 'markerMid' | 'markerStart' | 'mask' | 'maxHeight' | 'maxWidth' | 'minHeight' | 'minWidth' | 'msContentZoomChaining' | 'msContentZoomLimit' | 'msContentZoomLimitMax' | 'msContentZoomLimitMin' | 'msContentZoomSnap' | 'msContentZoomSnapPoints' | 'msContentZoomSnapType' | 'msContentZooming' | 'msFlowFrom' | 'msFlowInto' | 'msFontFeatureSettings' | 'msGridColumn' | 'msGridColumnAlign' | 'msGridColumnSpan' | 'msGridColumns' | 'msGridRow' | 'msGridRowAlign' | 'msGridRowSpan' | 'msGridRows' | 'msHighContrastAdjust' | 'msHyphenateLimitChars' | 'msHyphenateLimitLines' | 'msHyphenateLimitZone' | 'msHyphens' | 'msImeAlign' | 'msOverflowStyle' | 'msScrollChaining' | 'msScrollLimit' | 'msScrollLimitXMax' | 'msScrollLimitXMin' | 'msScrollLimitYMax' | 'msScrollLimitYMin' | 'msScrollRails' | 'msScrollSnapPointsX' | 'msScrollSnapPointsY' | 'msScrollSnapType' | 'msScrollSnapX' | 'msScrollSnapY' | 'msScrollTranslation' | 'msTextCombineHorizontal' | 'msTextSizeAdjust' | 'msTouchAction' | 'msTouchSelect' | 'msUserSelect' | 'msWrapFlow' | 'msWrapMargin' | 'msWrapThrough' | 'opacity' | 'order' | 'orphans' | 'outline' | 'outlineColor' | 'outlineStyle' | 'outlineWidth' | 'overflow' | 'overflowX' | 'overflowY' | 'padding' | 'paddingBottom' | 'paddingLeft' | 'paddingRight' | 'paddingTop' | 'pageBreakAfter' | 'pageBreakBefore' | 'pageBreakInside' | 'perspective' | 'perspectiveOrigin' | 'pointerEvents' | 'position' | 'quotes' | 'resize' | 'right' | 'rubyAlign' | 'rubyOverhang' | 'rubyPosition' | 'stopColor' | 'stopOpacity' | 'stroke' | 'strokeDasharray' | 'strokeDashoffset' | 'strokeLinecap' | 'strokeLinejoin' | 'strokeMiterlimit' | 'strokeOpacity' | 'strokeWidth' | 'tableLayout' | 'textAlign' | 'textAlignLast' | 'textAnchor' | 'textDecoration' | 'textIndent' | 'textJustify' | 'textKashida' | 'textKashidaSpace' | 'textOverflow' | 'textShadow' | 'textTransform' | 'textUnderlinePosition' | 'top' | 'touchAction' | 'transform' | 'transformOrigin' | 'transformStyle' | 'transition' | 'transitionDelay' | 'transitionDuration' | 'transitionProperty' | 'transitionTimingFunction' | 'unicodeBidi' | 'verticalAlign' | 'visibility' | 'webkitAlignContent' | 'webkitAlignItems' | 'webkitAlignSelf' | 'webkitAnimation' | 'webkitAnimationDelay' | 'webkitAnimationDirection' | 'webkitAnimationDuration' | 'webkitAnimationFillMode' | 'webkitAnimationIterationCount' | 'webkitAnimationName' | 'webkitAnimationPlayState' | 'webkitAnimationTimingFunction' | 'webkitAppearance' | 'webkitBackfaceVisibility' | 'webkitBackgroundClip' | 'webkitBackgroundOrigin' | 'webkitBackgroundSize' | 'webkitBorderBottomLeftRadius' | 'webkitBorderBottomRightRadius' | 'webkitBorderImage' | 'webkitBorderRadius' | 'webkitBorderTopLeftRadius' | 'webkitBorderTopRightRadius' | 'webkitBoxAlign' | 'webkitBoxDirection' | 'webkitBoxFlex' | 'webkitBoxOrdinalGroup' | 'webkitBoxOrient' | 'webkitBoxPack' | 'webkitBoxSizing' | 'webkitColumnBreakAfter' | 'webkitColumnBreakBefore' | 'webkitColumnBreakInside' | 'webkitColumnCount' | 'webkitColumnGap' | 'webkitColumnRule' | 'webkitColumnRuleColor' | 'webkitColumnRuleStyle' | 'webkitColumnRuleWidth' | 'webkitColumnSpan' | 'webkitColumnWidth' | 'webkitColumns' | 'webkitFilter' | 'webkitFlex' | 'webkitFlexBasis' | 'webkitFlexDirection' | 'webkitFlexFlow' | 'webkitFlexGrow' | 'webkitFlexShrink' | 'webkitFlexWrap' | 'webkitJustifyContent' | 'webkitOrder' | 'webkitPerspective' | 'webkitPerspectiveOrigin' | 'webkitTapHighlightColor' | 'webkitTextFillColor' | 'webkitTextSizeAdjust' | 'webkitTransform' | 'webkitTransformOrigin' | 'webkitTransformStyle' | 'webkitTransition' | 'webkitTransitionDelay' | 'webkitTransitionDuration' | 'webkitTransitionProperty' | 'webkitTransitionTimingFunction' | 'webkitUserModify' | 'webkitUserSelect' | 'webkitWritingMode' | 'whiteSpace' | 'widows' | 'width' | 'wordBreak' | 'wordSpacing' | 'wordWrap' | 'writingMode' | 'zIndex' | 'zoom'); // @public export type ElementAttrNames = ('abbr' | 'accept' | 'accept-charset' | 'accesskey' | 'action' | 'allowfullscreen' | 'alt' | 'autocomplete' | 'autofocus' | 'autoplay' | 'autosave' | 'checked' | 'cite' | 'cols' | 'colspan' | 'contenteditable' | 'controls' | 'coords' | 'crossorigin' | 'data' | 'datetime' | 'default' | 'dir' | 'dirname' | 'disabled' | 'download' | 'draggable' | 'dropzone' | 'enctype' | 'form' | 'formaction' | 'formenctype' | 'formmethod' | 'formnovalidate' | 'formtarget' | 'headers' | 'height' | 'hidden' | 'high' | 'href' | 'hreflang' | 'id' | 'inputmode' | 'integrity' | 'ismap' | 'kind' | 'label' | 'lang' | 'list' | 'loop' | 'low' | 'max' | 'maxlength' | 'media' | 'mediagroup' | 'method' | 'min' | 'minlength' | 'multiple' | 'muted' | 'name' | 'novalidate' | 'optimum' | 'pattern' | 'placeholder' | 'poster' | 'preload' | 'readonly' | 'rel' | 'required' | 'reversed' | 'rows' | 'rowspan' | 'sandbox' | 'scope' | 'selected' | 'shape' | 'size' | 'sizes' | 'span' | 'spellcheck' | 'src' | 'srcdoc' | 'srclang' | 'srcset' | 'start' | 'step' | 'tabindex' | 'target' | 'title' | 'type' | 'typemustmatch' | 'usemap' | 'value' | 'width' | 'wrap'); // @public export type ElementAttrs = (ElementBaseAttrs & ElementEventAttrs & ElementSpecialAttrs); // @public export type ElementBaseAttrs = { readonly [T in ElementAttrNames]?: string; }; // @public export type ElementDataset = { readonly [name: string]: string; }; // @public export type ElementEventAttrs = { readonly [T in keyof ElementEventMap]?: (this: HTMLElement, event: ElementEventMap[T]) => any; }; // @public export type ElementEventMap = { onabort: UIEvent; onauxclick: MouseEvent; onblur: FocusEvent; oncanplay: Event; oncanplaythrough: Event; onchange: Event; onclick: MouseEvent; oncontextmenu: PointerEvent; oncopy: ClipboardEvent; oncuechange: Event; oncut: ClipboardEvent; ondblclick: MouseEvent; ondrag: DragEvent; ondragend: DragEvent; ondragenter: DragEvent; ondragexit: DragEvent; ondragleave: DragEvent; ondragover: DragEvent; ondragstart: DragEvent; ondrop: DragEvent; ondurationchange: Event; onemptied: Event; onended: ErrorEvent; onerror: ErrorEvent; onfocus: FocusEvent; oninput: Event; oninvalid: Event; onkeydown: KeyboardEvent; onkeypress: KeyboardEvent; onkeyup: KeyboardEvent; onload: Event; onloadeddata: Event; onloadedmetadata: Event; onloadend: Event; onloadstart: Event; onmousedown: MouseEvent; onmouseenter: MouseEvent; onmouseleave: MouseEvent; onmousemove: MouseEvent; onmouseout: MouseEvent; onmouseover: MouseEvent; onmouseup: MouseEvent; onmousewheel: WheelEvent; onpaste: ClipboardEvent; onpause: Event; onplay: Event; onplaying: Event; onpointercancel: PointerEvent; onpointerdown: PointerEvent; onpointerenter: PointerEvent; onpointerleave: PointerEvent; onpointermove: PointerEvent; onpointerout: PointerEvent; onpointerover: PointerEvent; onpointerup: PointerEvent; onprogress: ProgressEvent; onratechange: Event; onreset: Event; onscroll: UIEvent; onseeked: Event; onseeking: Event; onselect: UIEvent; onselectstart: Event; onstalled: Event; onsubmit: Event; onsuspend: Event; ontimeupdate: Event; onvolumechange: Event; onwaiting: Event; }; // @public export type ElementInlineStyle = { readonly [T in CSSPropertyNames]?: string; }; // @public export type ElementSpecialAttrs = { readonly key?: string; readonly className?: string; readonly htmlFor?: string; readonly dataset?: ElementDataset; readonly style?: ElementInlineStyle; }; // @public export function h(tag: string, ...children: h.Child[]): VirtualElement; // @public (undocumented) export function h(tag: string, attrs: ElementAttrs, ...children: h.Child[]): VirtualElement; // @public export namespace h { export type Child = (string | VirtualNode | null) | Array; export interface IFactory { // (undocumented) (...children: Child[]): VirtualElement; // (undocumented) (attrs: ElementAttrs, ...children: Child[]): VirtualElement; } const // (undocumented) a: IFactory; const // (undocumented) abbr: IFactory; const // (undocumented) address: IFactory; const // (undocumented) area: IFactory; const // (undocumented) article: IFactory; const // (undocumented) aside: IFactory; const // (undocumented) audio: IFactory; const // (undocumented) b: IFactory; const // (undocumented) bdi: IFactory; const // (undocumented) bdo: IFactory; const // (undocumented) blockquote: IFactory; const // (undocumented) br: IFactory; const // (undocumented) button: IFactory; const // (undocumented) canvas: IFactory; const // (undocumented) caption: IFactory; const // (undocumented) cite: IFactory; const // (undocumented) code: IFactory; const // (undocumented) col: IFactory; const // (undocumented) colgroup: IFactory; const // (undocumented) data: IFactory; const // (undocumented) datalist: IFactory; const // (undocumented) dd: IFactory; const // (undocumented) del: IFactory; const // (undocumented) dfn: IFactory; const // (undocumented) div: IFactory; const // (undocumented) dl: IFactory; const // (undocumented) dt: IFactory; const // (undocumented) em: IFactory; const // (undocumented) embed: IFactory; const // (undocumented) fieldset: IFactory; const // (undocumented) figcaption: IFactory; const // (undocumented) figure: IFactory; const // (undocumented) footer: IFactory; const // (undocumented) form: IFactory; const // (undocumented) h1: IFactory; const // (undocumented) h2: IFactory; const // (undocumented) h3: IFactory; const // (undocumented) h4: IFactory; const // (undocumented) h5: IFactory; const // (undocumented) h6: IFactory; const // (undocumented) header: IFactory; const // (undocumented) hr: IFactory; const // (undocumented) i: IFactory; const // (undocumented) iframe: IFactory; const // (undocumented) img: IFactory; const // (undocumented) input: IFactory; const // (undocumented) ins: IFactory; const // (undocumented) kbd: IFactory; const // (undocumented) label: IFactory; const // (undocumented) legend: IFactory; const // (undocumented) li: IFactory; const // (undocumented) main: IFactory; const // (undocumented) map: IFactory; const // (undocumented) mark: IFactory; const // (undocumented) meter: IFactory; const // (undocumented) nav: IFactory; const // (undocumented) noscript: IFactory; const // (undocumented) object: IFactory; const // (undocumented) ol: IFactory; const // (undocumented) optgroup: IFactory; const // (undocumented) option: IFactory; const // (undocumented) output: IFactory; const // (undocumented) p: IFactory; const // (undocumented) param: IFactory; const // (undocumented) pre: IFactory; const // (undocumented) progress: IFactory; const // (undocumented) q: IFactory; const // (undocumented) rp: IFactory; const // (undocumented) rt: IFactory; const // (undocumented) ruby: IFactory; const // (undocumented) s: IFactory; const // (undocumented) samp: IFactory; const // (undocumented) section: IFactory; const // (undocumented) select: IFactory; const // (undocumented) small: IFactory; const // (undocumented) source: IFactory; const // (undocumented) span: IFactory; const // (undocumented) strong: IFactory; const // (undocumented) sub: IFactory; const // (undocumented) summary: IFactory; const // (undocumented) sup: IFactory; const // (undocumented) table: IFactory; const // (undocumented) tbody: IFactory; const // (undocumented) td: IFactory; const // (undocumented) textarea: IFactory; const // (undocumented) tfoot: IFactory; const // (undocumented) th: IFactory; const // (undocumented) thead: IFactory; const // (undocumented) time: IFactory; const // (undocumented) title: IFactory; const // (undocumented) tr: IFactory; const // (undocumented) track: IFactory; const // (undocumented) u: IFactory; const // (undocumented) ul: IFactory; const // (undocumented) var_: IFactory; const // (undocumented) video: IFactory; const // (undocumented) wbr: IFactory; } // @public export namespace VirtualDOM { export function realize(node: VirtualElement): HTMLElement; export function render(content: VirtualNode | ReadonlyArray | null, host: HTMLElement): void; } // @public export class VirtualElement { constructor(tag: string, attrs: ElementAttrs, children: ReadonlyArray); readonly attrs: ElementAttrs; readonly children: ReadonlyArray; readonly tag: string; readonly type: 'element'; } // @public export type VirtualNode = VirtualElement | VirtualText; // @public export class VirtualText { constructor(content: string); readonly content: string; readonly type: 'text'; } // (No @packageDocumentation comment for this package) ``` lumino-2021.12.13/review/api/widgets.api.md000066400000000000000000001164031415564225700202400ustar00rootroot00000000000000## API Report File for "@lumino/widgets" > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts import { CommandRegistry } from '@lumino/commands'; import { ConflatableMessage } from '@lumino/messaging'; import { ElementDataset } from '@lumino/virtualdom'; import { ElementInlineStyle } from '@lumino/virtualdom'; import { h } from '@lumino/virtualdom'; import { IDisposable } from '@lumino/disposable'; import { IIterable } from '@lumino/algorithm'; import { IIterator } from '@lumino/algorithm'; import { IMessageHandler } from '@lumino/messaging'; import { IObservableDisposable } from '@lumino/disposable'; import { ISignal } from '@lumino/signaling'; import { Message } from '@lumino/messaging'; import { ReadonlyJSONObject } from '@lumino/coreutils'; import { VirtualElement } from '@lumino/virtualdom'; // @public export namespace BoxEngine { export function adjust(sizers: ArrayLike, index: number, delta: number): void; export function calc(sizers: ArrayLike, space: number): number; } // @public export class BoxLayout extends PanelLayout { constructor(options?: BoxLayout.IOptions); alignment: BoxLayout.Alignment; protected attachWidget(index: number, widget: Widget): void; protected detachWidget(index: number, widget: Widget): void; direction: BoxLayout.Direction; dispose(): void; protected init(): void; protected moveWidget(fromIndex: number, toIndex: number, widget: Widget): void; protected onBeforeAttach(msg: Message): void; protected onBeforeShow(msg: Message): void; protected onChildHidden(msg: Widget.ChildMessage): void; protected onChildShown(msg: Widget.ChildMessage): void; protected onFitRequest(msg: Message): void; protected onResize(msg: Widget.ResizeMessage): void; protected onUpdateRequest(msg: Message): void; spacing: number; } // @public export namespace BoxLayout { export type Alignment = 'start' | 'center' | 'end' | 'justify'; export type Direction = ('left-to-right' | 'right-to-left' | 'top-to-bottom' | 'bottom-to-top'); export function getSizeBasis(widget: Widget): number; export function getStretch(widget: Widget): number; export interface IOptions { alignment?: Alignment; direction?: Direction; spacing?: number; } export function setSizeBasis(widget: Widget, value: number): void; export function setStretch(widget: Widget, value: number): void; } // @public export class BoxPanel extends Panel { constructor(options?: BoxPanel.IOptions); alignment: BoxPanel.Alignment; direction: BoxPanel.Direction; protected onChildAdded(msg: Widget.ChildMessage): void; protected onChildRemoved(msg: Widget.ChildMessage): void; spacing: number; } // @public export namespace BoxPanel { export type Alignment = BoxLayout.Alignment; export type Direction = BoxLayout.Direction; export function getSizeBasis(widget: Widget): number; export function getStretch(widget: Widget): number; export interface IOptions { alignment?: Alignment; direction?: Direction; layout?: BoxLayout; spacing?: number; } export function setSizeBasis(widget: Widget, value: number): void; export function setStretch(widget: Widget, value: number): void; } // @public export class BoxSizer { done: boolean; maxSize: number; minSize: number; size: number; sizeHint: number; stretch: number; } // @public export class CommandPalette extends Widget { constructor(options: CommandPalette.IOptions); addItem(options: CommandPalette.IItemOptions): CommandPalette.IItem; clearItems(): void; readonly commands: CommandRegistry; readonly contentNode: HTMLUListElement; dispose(): void; handleEvent(event: Event): void; readonly inputNode: HTMLInputElement; readonly items: ReadonlyArray; protected onActivateRequest(msg: Message): void; protected onAfterDetach(msg: Message): void; protected onBeforeAttach(msg: Message): void; protected onUpdateRequest(msg: Message): void; refresh(): void; removeItem(item: CommandPalette.IItem): void; removeItemAt(index: number): void; readonly renderer: CommandPalette.IRenderer; readonly searchNode: HTMLDivElement; } // @public export namespace CommandPalette { export interface IEmptyMessageRenderData { query: string; } export interface IHeaderRenderData { readonly category: string; readonly indices: ReadonlyArray | null; } export interface IItem { readonly args: ReadonlyJSONObject; readonly caption: string; readonly category: string; readonly className: string; readonly command: string; readonly dataset: CommandRegistry.Dataset; readonly iconClass: string; readonly iconLabel: string; readonly isEnabled: boolean; readonly isToggled: boolean; readonly isVisible: boolean; readonly keyBinding: CommandRegistry.IKeyBinding | null; readonly label: string; readonly rank: number; } export interface IItemOptions { args?: ReadonlyJSONObject; category: string; command: string; rank?: number; } export interface IItemRenderData { readonly active: boolean; readonly indices: ReadonlyArray | null; readonly item: IItem; } export interface IOptions { commands: CommandRegistry; renderer?: IRenderer; } export interface IRenderer { renderEmptyMessage(data: IEmptyMessageRenderData): VirtualElement; renderHeader(data: IHeaderRenderData): VirtualElement; renderItem(data: IItemRenderData): VirtualElement; } export class Renderer implements IRenderer { createIconClass(data: IItemRenderData): string; createItemClass(data: IItemRenderData): string; createItemDataset(data: IItemRenderData): ElementDataset; formatEmptyMessage(data: IEmptyMessageRenderData): h.Child; formatHeader(data: IHeaderRenderData): h.Child; formatItemCaption(data: IItemRenderData): h.Child; formatItemLabel(data: IItemRenderData): h.Child; formatItemShortcut(data: IItemRenderData): h.Child; renderEmptyMessage(data: IEmptyMessageRenderData): VirtualElement; renderHeader(data: IHeaderRenderData): VirtualElement; renderItem(data: IItemRenderData): VirtualElement; renderItemCaption(data: IItemRenderData): VirtualElement; renderItemContent(data: IItemRenderData): VirtualElement; renderItemIcon(data: IItemRenderData): VirtualElement; renderItemLabel(data: IItemRenderData): VirtualElement; renderItemShortcut(data: IItemRenderData): VirtualElement; } const defaultRenderer: Renderer; } // @public export class ContextMenu { constructor(options: ContextMenu.IOptions); addItem(options: ContextMenu.IItemOptions): IDisposable; readonly menu: Menu; open(event: MouseEvent): boolean; } // @public export namespace ContextMenu { export interface IItemOptions extends Menu.IItemOptions { rank?: number; selector: string; } export interface IOptions { commands: CommandRegistry; renderer?: Menu.IRenderer; } } // @public export class DockLayout extends Layout { constructor(options: DockLayout.IOptions); addWidget(widget: Widget, options?: DockLayout.IAddOptions): void; protected attachWidget(widget: Widget): void; protected detachWidget(widget: Widget): void; dispose(): void; handles(): IIterator; hitTestTabAreas(clientX: number, clientY: number): DockLayout.ITabAreaGeometry | null; protected init(): void; readonly isEmpty: boolean; iter(): IIterator; moveHandle(handle: HTMLDivElement, offsetX: number, offsetY: number): void; protected onBeforeAttach(msg: Message): void; protected onBeforeShow(msg: Message): void; protected onChildHidden(msg: Widget.ChildMessage): void; protected onChildShown(msg: Widget.ChildMessage): void; protected onFitRequest(msg: Message): void; protected onResize(msg: Widget.ResizeMessage): void; protected onUpdateRequest(msg: Message): void; removeWidget(widget: Widget): void; readonly renderer: DockLayout.IRenderer; restoreLayout(config: DockLayout.ILayoutConfig): void; saveLayout(): DockLayout.ILayoutConfig; selectedWidgets(): IIterator; spacing: number; tabBars(): IIterator>; widgets(): IIterator; } // @public export namespace DockLayout { export type AreaConfig = ITabAreaConfig | ISplitAreaConfig; export interface IAddOptions { mode?: InsertMode; ref?: Widget | null; } export interface ILayoutConfig { main: AreaConfig | null; } export type InsertMode = ( /** * The area to the top of the reference widget. * * The widget will be inserted just above the reference widget. * * If the reference widget is null or invalid, the widget will be * inserted at the top edge of the dock layout. */ 'split-top' | /** * The area to the left of the reference widget. * * The widget will be inserted just left of the reference widget. * * If the reference widget is null or invalid, the widget will be * inserted at the left edge of the dock layout. */ 'split-left' | /** * The area to the right of the reference widget. * * The widget will be inserted just right of the reference widget. * * If the reference widget is null or invalid, the widget will be * inserted at the right edge of the dock layout. */ 'split-right' | /** * The area to the bottom of the reference widget. * * The widget will be inserted just below the reference widget. * * If the reference widget is null or invalid, the widget will be * inserted at the bottom edge of the dock layout. */ 'split-bottom' | /** * The tab position before the reference widget. * * The widget will be added as a tab before the reference widget. * * If the reference widget is null or invalid, a sensible default * will be used. */ 'tab-before' | /** * The tab position after the reference widget. * * The widget will be added as a tab after the reference widget. * * If the reference widget is null or invalid, a sensible default * will be used. */ 'tab-after'); export interface IOptions { renderer: IRenderer; spacing?: number; } export interface IRenderer { createHandle(): HTMLDivElement; createTabBar(): TabBar; } export interface ISplitAreaConfig { children: AreaConfig[]; orientation: 'horizontal' | 'vertical'; sizes: number[]; type: 'split-area'; } export interface ITabAreaConfig { currentIndex: number; type: 'tab-area'; widgets: Widget[]; } export interface ITabAreaGeometry { bottom: number; height: number; left: number; right: number; tabBar: TabBar; top: number; width: number; x: number; y: number; } } // @public export class DockPanel extends Widget { constructor(options?: DockPanel.IOptions); activateWidget(widget: Widget): void; addWidget(widget: Widget, options?: DockPanel.IAddOptions): void; dispose(): void; handleEvent(event: Event): void; handles(): IIterator; readonly isEmpty: boolean; readonly layoutModified: ISignal; mode: DockPanel.Mode; protected onAfterDetach(msg: Message): void; protected onBeforeAttach(msg: Message): void; protected onChildAdded(msg: Widget.ChildMessage): void; protected onChildRemoved(msg: Widget.ChildMessage): void; readonly overlay: DockPanel.IOverlay; processMessage(msg: Message): void; readonly renderer: DockPanel.IRenderer; restoreLayout(config: DockPanel.ILayoutConfig): void; saveLayout(): DockPanel.ILayoutConfig; selectedWidgets(): IIterator; selectWidget(widget: Widget): void; spacing: number; tabBars(): IIterator>; tabsMovable: boolean; widgets(): IIterator; } // @public export namespace DockPanel { export type IAddOptions = DockLayout.IAddOptions; export interface IEdges { bottom: number; left: number; right: number; top: number; } export type ILayoutConfig = DockLayout.ILayoutConfig; export type InsertMode = DockLayout.InsertMode; export interface IOptions { edges?: IEdges; mode?: DockPanel.Mode; overlay?: IOverlay; renderer?: IRenderer; spacing?: number; tabsMovable?: boolean; } export interface IOverlay { hide(delay: number): void; readonly node: HTMLDivElement; show(geo: IOverlayGeometry): void; } export interface IOverlayGeometry { bottom: number; left: number; right: number; top: number; } export type IRenderer = DockLayout.IRenderer; export type Mode = ( /** * The single document mode. * * In this mode, only a single widget is visible at a time, and that * widget fills the available layout space. No tab bars are visible. */ 'single-document' | /** * The multiple document mode. * * In this mode, multiple documents are displayed in separate tab * areas, and those areas can be individually resized by the user. */ 'multiple-document'); export class Overlay implements IOverlay { constructor(); hide(delay: number): void; readonly node: HTMLDivElement; show(geo: IOverlayGeometry): void; } export class Renderer implements IRenderer { createHandle(): HTMLDivElement; createTabBar(): TabBar; } const defaultRenderer: Renderer; } // @public export class FocusTracker implements IDisposable { constructor(); readonly activeChanged: ISignal>; readonly activeWidget: T | null; add(widget: T): void; readonly currentChanged: ISignal>; readonly currentWidget: T | null; dispose(): void; focusNumber(widget: T): number; handleEvent(event: Event): void; has(widget: T): boolean; readonly isDisposed: boolean; remove(widget: T): void; readonly widgets: ReadonlyArray; } // @public export namespace FocusTracker { export interface IChangedArgs { newValue: T | null; oldValue: T | null; } } // @public export class GridLayout extends Layout { constructor(options?: GridLayout.IOptions); addWidget(widget: Widget): void; protected attachWidget(widget: Widget): void; columnCount: number; columnSpacing: number; columnStretch(index: number): number; protected detachWidget(widget: Widget): void; dispose(): void; protected init(): void; iter(): IIterator; protected onBeforeAttach(msg: Message): void; protected onBeforeShow(msg: Message): void; protected onChildHidden(msg: Widget.ChildMessage): void; protected onChildShown(msg: Widget.ChildMessage): void; protected onFitRequest(msg: Message): void; protected onResize(msg: Widget.ResizeMessage): void; protected onUpdateRequest(msg: Message): void; removeWidget(widget: Widget): void; rowCount: number; rowSpacing: number; rowStretch(index: number): number; setColumnStretch(index: number, value: number): void; setRowStretch(index: number, value: number): void; } // @public export namespace GridLayout { export function getCellConfig(widget: Widget): ICellConfig; export interface ICellConfig { readonly column: number; readonly columnSpan: number; readonly row: number; readonly rowSpan: number; } export interface IOptions extends Layout.IOptions { columnCount?: number; columnSpacing?: number; rowCount?: number; rowSpacing?: number; } export function setCellConfig(widget: Widget, value: Partial): void; } // @public export abstract class Layout implements IIterable, IDisposable { constructor(options?: Layout.IOptions); dispose(): void; fitPolicy: Layout.FitPolicy; protected init(): void; readonly isDisposed: boolean; abstract iter(): IIterator; protected onAfterAttach(msg: Message): void; protected onAfterDetach(msg: Message): void; protected onAfterHide(msg: Message): void; protected onAfterShow(msg: Message): void; protected onBeforeAttach(msg: Message): void; protected onBeforeDetach(msg: Message): void; protected onBeforeHide(msg: Message): void; protected onBeforeShow(msg: Message): void; protected onChildHidden(msg: Widget.ChildMessage): void; protected onChildRemoved(msg: Widget.ChildMessage): void; protected onChildShown(msg: Widget.ChildMessage): void; protected onFitRequest(msg: Message): void; protected onResize(msg: Widget.ResizeMessage): void; protected onUpdateRequest(msg: Message): void; parent: Widget | null; processParentMessage(msg: Message): void; abstract removeWidget(widget: Widget): void; } // @public export namespace Layout { export type FitPolicy = ( /** * No size constraint will be applied to the parent widget. */ 'set-no-constraint' | /** * The computed min size will be applied to the parent widget. */ 'set-min-size'); export function getHorizontalAlignment(widget: Widget): HorizontalAlignment; export function getVerticalAlignment(widget: Widget): VerticalAlignment; export type HorizontalAlignment = 'left' | 'center' | 'right'; export interface IOptions { fitPolicy?: FitPolicy; } export function setHorizontalAlignment(widget: Widget, value: HorizontalAlignment): void; export function setVerticalAlignment(widget: Widget, value: VerticalAlignment): void; export type VerticalAlignment = 'top' | 'center' | 'bottom'; } // @public export class LayoutItem implements IDisposable { constructor(widget: Widget); dispose(): void; fit(): void; readonly isAttached: boolean; readonly isDisposed: boolean; readonly isHidden: boolean; readonly isVisible: boolean; readonly maxHeight: number; readonly maxWidth: number; readonly minHeight: number; readonly minWidth: number; update(left: number, top: number, width: number, height: number): void; readonly widget: Widget; } // @public export class Menu extends Widget { constructor(options: Menu.IOptions); readonly aboutToClose: ISignal; activateNextItem(): void; activatePreviousItem(): void; activeIndex: number; activeItem: Menu.IItem | null; addItem(options: Menu.IItemOptions): Menu.IItem; readonly childMenu: Menu | null; clearItems(): void; readonly commands: CommandRegistry; readonly contentNode: HTMLUListElement; dispose(): void; handleEvent(event: Event): void; insertItem(index: number, options: Menu.IItemOptions): Menu.IItem; readonly items: ReadonlyArray; readonly leafMenu: Menu; readonly menuRequested: ISignal; protected onActivateRequest(msg: Message): void; protected onAfterDetach(msg: Message): void; protected onBeforeAttach(msg: Message): void; protected onCloseRequest(msg: Message): void; protected onUpdateRequest(msg: Message): void; open(x: number, y: number, options?: Menu.IOpenOptions): void; readonly parentMenu: Menu | null; removeItem(item: Menu.IItem): void; removeItemAt(index: number): void; readonly renderer: Menu.IRenderer; readonly rootMenu: Menu; triggerActiveItem(): void; } // @public export namespace Menu { export interface IItem { readonly args: ReadonlyJSONObject; readonly caption: string; readonly className: string; readonly command: string; readonly dataset: CommandRegistry.Dataset; // @deprecated (undocumented) readonly icon: string; readonly iconClass: string; readonly iconLabel: string; readonly isEnabled: boolean; readonly isToggled: boolean; readonly isVisible: boolean; readonly keyBinding: CommandRegistry.IKeyBinding | null; readonly label: string; readonly mnemonic: number; readonly submenu: Menu | null; readonly type: ItemType; } export interface IItemOptions { args?: ReadonlyJSONObject; command?: string; submenu?: Menu | null; type?: ItemType; } export interface IOpenOptions { forceX?: boolean; forceY?: boolean; } export interface IOptions { commands: CommandRegistry; renderer?: IRenderer; } export interface IRenderData { readonly active: boolean; readonly collapsed: boolean; readonly item: IItem; } export interface IRenderer { renderItem(data: IRenderData): VirtualElement; } export type ItemType = 'command' | 'submenu' | 'separator'; export class Renderer implements IRenderer { constructor(); createIconClass(data: IRenderData): string; createItemClass(data: IRenderData): string; createItemDataset(data: IRenderData): ElementDataset; formatLabel(data: IRenderData): h.Child; formatShortcut(data: IRenderData): h.Child; renderIcon(data: IRenderData): VirtualElement; renderItem(data: IRenderData): VirtualElement; renderLabel(data: IRenderData): VirtualElement; renderShortcut(data: IRenderData): VirtualElement; renderSubmenu(data: IRenderData): VirtualElement; } const defaultRenderer: Renderer; } // @public export class MenuBar extends Widget { constructor(options?: MenuBar.IOptions); activeIndex: number; activeMenu: Menu | null; addMenu(menu: Menu): void; readonly childMenu: Menu | null; clearMenus(): void; readonly contentNode: HTMLUListElement; dispose(): void; handleEvent(event: Event): void; insertMenu(index: number, menu: Menu): void; readonly menus: ReadonlyArray; protected onActivateRequest(msg: Message): void; protected onAfterDetach(msg: Message): void; protected onBeforeAttach(msg: Message): void; protected onUpdateRequest(msg: Message): void; openActiveMenu(): void; removeMenu(menu: Menu): void; removeMenuAt(index: number): void; readonly renderer: MenuBar.IRenderer; } // @public export namespace MenuBar { export interface IOptions { renderer?: IRenderer; } export interface IRenderData { readonly active: boolean; readonly title: Title; } export interface IRenderer { renderItem(data: IRenderData): VirtualElement; } export class Renderer implements IRenderer { constructor(); createIconClass(data: IRenderData): string; createItemClass(data: IRenderData): string; createItemDataset(data: IRenderData): ElementDataset; formatLabel(data: IRenderData): h.Child; renderIcon(data: IRenderData): VirtualElement; renderItem(data: IRenderData): VirtualElement; renderLabel(data: IRenderData): VirtualElement; } const defaultRenderer: Renderer; } // @public export class Panel extends Widget { constructor(options?: Panel.IOptions); addWidget(widget: Widget): void; insertWidget(index: number, widget: Widget): void; readonly widgets: ReadonlyArray; } // @public export namespace Panel { export interface IOptions { layout?: PanelLayout; } } // @public export class PanelLayout extends Layout { addWidget(widget: Widget): void; protected attachWidget(index: number, widget: Widget): void; protected detachWidget(index: number, widget: Widget): void; dispose(): void; protected init(): void; insertWidget(index: number, widget: Widget): void; iter(): IIterator; protected moveWidget(fromIndex: number, toIndex: number, widget: Widget): void; removeWidget(widget: Widget): void; removeWidgetAt(index: number): void; readonly widgets: ReadonlyArray; } // @public export class ScrollBar extends Widget { constructor(options?: ScrollBar.IOptions); readonly decrementNode: HTMLDivElement; handleEvent(event: Event): void; readonly incrementNode: HTMLDivElement; maximum: number; protected onAfterDetach(msg: Message): void; protected onBeforeAttach(msg: Message): void; protected onUpdateRequest(msg: Message): void; orientation: ScrollBar.Orientation; page: number; readonly pageRequested: ISignal; readonly stepRequested: ISignal; readonly thumbMoved: ISignal; readonly thumbNode: HTMLDivElement; readonly trackNode: HTMLDivElement; value: number; } // @public export namespace ScrollBar { export interface IOptions { maximum?: number; orientation?: Orientation; page?: number; value?: number; } export type Orientation = 'horizontal' | 'vertical'; } // @public export class SingletonLayout extends Layout { protected attachWidget(widget: Widget): void; protected detachWidget(widget: Widget): void; dispose(): void; protected init(): void; iter(): IIterator; removeWidget(widget: Widget): void; widget: Widget | null; } // @public export class SplitLayout extends PanelLayout { constructor(options: SplitLayout.IOptions); alignment: SplitLayout.Alignment; protected attachWidget(index: number, widget: Widget): void; protected detachWidget(index: number, widget: Widget): void; dispose(): void; readonly handles: ReadonlyArray; protected init(): void; moveHandle(index: number, position: number): void; protected moveWidget(fromIndex: number, toIndex: number, widget: Widget): void; protected onBeforeAttach(msg: Message): void; protected onBeforeShow(msg: Message): void; protected onChildHidden(msg: Widget.ChildMessage): void; protected onChildShown(msg: Widget.ChildMessage): void; protected onFitRequest(msg: Message): void; protected onResize(msg: Widget.ResizeMessage): void; protected onUpdateRequest(msg: Message): void; orientation: SplitLayout.Orientation; relativeSizes(): number[]; readonly renderer: SplitLayout.IRenderer; setRelativeSizes(sizes: number[]): void; spacing: number; } // @public export namespace SplitLayout { export type Alignment = 'start' | 'center' | 'end' | 'justify'; export function getStretch(widget: Widget): number; export interface IOptions { alignment?: Alignment; orientation?: Orientation; renderer: IRenderer; spacing?: number; } export interface IRenderer { createHandle(): HTMLDivElement; } export type Orientation = 'horizontal' | 'vertical'; export function setStretch(widget: Widget, value: number): void; } // @public export class SplitPanel extends Panel { constructor(options?: SplitPanel.IOptions); alignment: SplitPanel.Alignment; dispose(): void; handleEvent(event: Event): void; readonly handles: ReadonlyArray; protected onAfterDetach(msg: Message): void; protected onBeforeAttach(msg: Message): void; protected onChildAdded(msg: Widget.ChildMessage): void; protected onChildRemoved(msg: Widget.ChildMessage): void; orientation: SplitPanel.Orientation; relativeSizes(): number[]; readonly renderer: SplitPanel.IRenderer; setRelativeSizes(sizes: number[]): void; spacing: number; } // @public export namespace SplitPanel { export type Alignment = SplitLayout.Alignment; export function getStretch(widget: Widget): number; export interface IOptions { alignment?: Alignment; layout?: SplitLayout; orientation?: Orientation; renderer?: IRenderer; spacing?: number; } export type IRenderer = SplitLayout.IRenderer; export type Orientation = SplitLayout.Orientation; const defaultRenderer: Renderer; export class Renderer implements IRenderer { createHandle(): HTMLDivElement; } export function setStretch(widget: Widget, value: number): void; } // @public export class StackedLayout extends PanelLayout { protected attachWidget(index: number, widget: Widget): void; protected detachWidget(index: number, widget: Widget): void; dispose(): void; protected moveWidget(fromIndex: number, toIndex: number, widget: Widget): void; protected onBeforeAttach(msg: Message): void; protected onBeforeShow(msg: Message): void; protected onChildHidden(msg: Widget.ChildMessage): void; protected onChildShown(msg: Widget.ChildMessage): void; protected onFitRequest(msg: Message): void; protected onResize(msg: Widget.ResizeMessage): void; protected onUpdateRequest(msg: Message): void; } // @public export class StackedPanel extends Panel { constructor(options?: StackedPanel.IOptions); protected onChildAdded(msg: Widget.ChildMessage): void; protected onChildRemoved(msg: Widget.ChildMessage): void; readonly widgetRemoved: ISignal; } // @public export namespace StackedPanel { export interface IOptions { layout?: StackedLayout; } } // @public export class TabBar extends Widget { constructor(options?: TabBar.IOptions); addTab(value: Title | Title.IOptions): Title; allowDeselect: boolean; clearTabs(): void; readonly contentNode: HTMLUListElement; readonly currentChanged: ISignal>; currentIndex: number; currentTitle: Title | null; dispose(): void; handleEvent(event: Event): void; insertBehavior: TabBar.InsertBehavior; insertTab(index: number, value: Title | Title.IOptions): Title; protected onAfterDetach(msg: Message): void; protected onBeforeAttach(msg: Message): void; protected onUpdateRequest(msg: Message): void; orientation: TabBar.Orientation; releaseMouse(): void; removeBehavior: TabBar.RemoveBehavior; removeTab(title: Title): void; removeTabAt(index: number): void; readonly renderer: TabBar.IRenderer; readonly tabActivateRequested: ISignal>; readonly tabCloseRequested: ISignal>; readonly tabDetachRequested: ISignal>; readonly tabMoved: ISignal>; tabsMovable: boolean; readonly titles: ReadonlyArray>; } // @public export namespace TabBar { export interface ICurrentChangedArgs { readonly currentIndex: number; readonly currentTitle: Title | null; readonly previousIndex: number; readonly previousTitle: Title | null; } export type InsertBehavior = ( /** * The selected tab will not be changed. */ 'none' | /** * The inserted tab will be selected. */ 'select-tab' | /** * The inserted tab will be selected if the current tab is null. */ 'select-tab-if-needed'); export interface IOptions { allowDeselect?: boolean; insertBehavior?: TabBar.InsertBehavior; orientation?: TabBar.Orientation; removeBehavior?: TabBar.RemoveBehavior; renderer?: IRenderer; tabsMovable?: boolean; } export interface IRenderData { readonly current: boolean; readonly title: Title; readonly zIndex: number; } export interface IRenderer { readonly closeIconSelector: string; renderTab(data: IRenderData): VirtualElement; } export interface ITabActivateRequestedArgs { readonly index: number; readonly title: Title; } export interface ITabCloseRequestedArgs { readonly index: number; readonly title: Title; } export interface ITabDetachRequestedArgs { readonly clientX: number; readonly clientY: number; readonly index: number; readonly tab: HTMLElement; readonly title: Title; } export interface ITabMovedArgs { readonly fromIndex: number; readonly title: Title; readonly toIndex: number; } export type Orientation = ( /** * The tabs are arranged in a single row, left-to-right. * * The tab text orientation is horizontal. */ 'horizontal' | /** * The tabs are arranged in a single column, top-to-bottom. * * The tab text orientation is horizontal. */ 'vertical'); export type RemoveBehavior = ( /** * No tab will be selected. */ 'none' | /** * The tab after the removed tab will be selected if possible. */ 'select-tab-after' | /** * The tab before the removed tab will be selected if possible. */ 'select-tab-before' | /** * The previously selected tab will be selected if possible. */ 'select-previous-tab'); export class Renderer implements IRenderer { constructor(); readonly closeIconSelector: string; createIconClass(data: IRenderData): string; createTabClass(data: IRenderData): string; createTabDataset(data: IRenderData): ElementDataset; createTabKey(data: IRenderData): string; createTabStyle(data: IRenderData): ElementInlineStyle; renderCloseIcon(data: IRenderData): VirtualElement; renderIcon(data: IRenderData): VirtualElement; renderLabel(data: IRenderData): VirtualElement; renderTab(data: IRenderData): VirtualElement; } const defaultRenderer: Renderer; } // @public export class TabPanel extends Widget { constructor(options?: TabPanel.IOptions); addWidget(widget: Widget): void; readonly currentChanged: ISignal; currentIndex: number; currentWidget: Widget | null; insertWidget(index: number, widget: Widget): void; readonly stackedPanel: StackedPanel; readonly tabBar: TabBar; tabPlacement: TabPanel.TabPlacement; tabsMovable: boolean; readonly widgets: ReadonlyArray; } // @public export namespace TabPanel { export interface ICurrentChangedArgs { currentIndex: number; currentWidget: Widget | null; previousIndex: number; previousWidget: Widget | null; } export interface IOptions { renderer?: TabBar.IRenderer; tabPlacement?: TabPlacement; tabsMovable?: boolean; } export type TabPlacement = ( /** * The tabs are placed as a row above the content. */ 'top' | /** * The tabs are placed as a column to the left of the content. */ 'left' | /** * The tabs are placed as a column to the right of the content. */ 'right' | /** * The tabs are placed as a row below the content. */ 'bottom'); } // @public export class Title { constructor(options: Title.IOptions); caption: string; readonly changed: ISignal; className: string; closable: boolean; dataset: Title.Dataset; // @deprecated (undocumented) icon: string; iconClass: string; iconLabel: string; label: string; mnemonic: number; readonly owner: T; } // @public export namespace Title { export type Dataset = { readonly [key: string]: string; }; export interface IOptions { caption?: string; className?: string; closable?: boolean; dataset?: Dataset; // @deprecated (undocumented) icon?: string; iconClass?: string; iconLabel?: string; label?: string; mnemonic?: number; owner: T; } } // @public export class Widget implements IMessageHandler, IObservableDisposable { constructor(options?: Widget.IOptions); activate(): void; addClass(name: string): void; children(): IIterator; clearFlag(flag: Widget.Flag): void; close(): void; contains(widget: Widget): boolean; readonly dataset: DOMStringMap; dispose(): void; readonly disposed: ISignal; fit(): void; hasClass(name: string): boolean; hide(): void; id: string; readonly isAttached: boolean; readonly isDisposed: boolean; readonly isHidden: boolean; readonly isVisible: boolean; layout: Layout | null; readonly node: HTMLElement; protected notifyLayout(msg: Message): void; protected onActivateRequest(msg: Message): void; protected onAfterAttach(msg: Message): void; protected onAfterDetach(msg: Message): void; protected onAfterHide(msg: Message): void; protected onAfterShow(msg: Message): void; protected onBeforeAttach(msg: Message): void; protected onBeforeDetach(msg: Message): void; protected onBeforeHide(msg: Message): void; protected onBeforeShow(msg: Message): void; protected onChildAdded(msg: Widget.ChildMessage): void; protected onChildRemoved(msg: Widget.ChildMessage): void; protected onCloseRequest(msg: Message): void; protected onFitRequest(msg: Message): void; protected onResize(msg: Widget.ResizeMessage): void; protected onUpdateRequest(msg: Message): void; parent: Widget | null; processMessage(msg: Message): void; removeClass(name: string): void; setFlag(flag: Widget.Flag): void; setHidden(hidden: boolean): void; show(): void; testFlag(flag: Widget.Flag): boolean; readonly title: Title; toggleClass(name: string, force?: boolean): boolean; update(): void; } // @public export namespace Widget { export function attach(widget: Widget, host: HTMLElement, ref?: HTMLElement | null): void; export class ChildMessage extends Message { constructor(type: string, child: Widget); readonly child: Widget; } export function detach(widget: Widget): void; export enum Flag { DisallowLayout = 16, IsAttached = 2, IsDisposed = 1, IsHidden = 4, IsVisible = 8 } export interface IOptions { node?: HTMLElement; } export namespace Msg { const BeforeShow: Message; const AfterShow: Message; const BeforeHide: Message; const AfterHide: Message; const BeforeAttach: Message; const AfterAttach: Message; const BeforeDetach: Message; const AfterDetach: Message; const ParentChanged: Message; const UpdateRequest: ConflatableMessage; const FitRequest: ConflatableMessage; const ActivateRequest: ConflatableMessage; const CloseRequest: ConflatableMessage; } export class ResizeMessage extends Message { constructor(width: number, height: number); readonly height: number; readonly width: number; } export namespace ResizeMessage { const UnknownSize: ResizeMessage; } } // (No @packageDocumentation comment for this package) ``` lumino-2021.12.13/scripts/000077500000000000000000000000001415564225700151105ustar00rootroot00000000000000lumino-2021.12.13/scripts/format-changelog.js000066400000000000000000000030571415564225700206700ustar00rootroot00000000000000const path = require('path'); const fs = require('fs'); const utils = require('@jupyterlab/buildutils'); // Add version changes to the changelog entry const package = JSON.parse( fs.readFileSync('package.json', { encoding: 'utf-8' }) ); const versions = []; let changelog = fs.readFileSync('CHANGELOG.md', { encoding: 'utf-8' }); const firstIndex = changelog.indexOf(''); const lastIndex = changelog.indexOf(''); const entry = changelog.slice(firstIndex, lastIndex); // For each package, compare the local version to the published version fs.readdirSync('packages').forEach(pkgName => { const localPath = path.join('packages', pkgName, 'package.json'); const localPackage = JSON.parse( fs.readFileSync(localPath, { encoding: 'utf-8' }) ); const name = localPackage.name; const version = localPackage.version; const remoteVersion = utils.run( `npm info ${name} version`, { stdio: 'pipe' }, true ); if (version !== remoteVersion) { versions.push(` ${name}: ${remoteVersion} => ${version}`); } }); // Splice the entry if (versions) { const lines = entry.split('\n'); let index = -1; lines.forEach((line, i) => { if (line.startsWith('([Full Changelog]')) { index = i + 1; } }); if (index != -1) { const newEntry = lines .slice(0, index) .concat([''], versions, lines.slice(index, -1), ['']); changelog = changelog.replace(entry, newEntry.join('\n')); } } fs.writeFileSync('CHANGELOG.md', changelog, { encoding: 'utf-8' }); lumino-2021.12.13/scripts/tag-versions.js000066400000000000000000000012051415564225700200650ustar00rootroot00000000000000const path = require('path'); const fs = require('fs'); const utils = require('@jupyterlab/buildutils'); // Get the list of tags const tags = utils.run('git tag', { stdio: 'pipe' }, true).split('\n'); // For each package, compare the local version to the published version fs.readdirSync('packages').forEach(pkgName => { const localPath = path.join('packages', pkgName, 'package.json'); const localPackage = JSON.parse( fs.readFileSync(localPath, { encoding: 'utf-8' }) ); const tag = `${localPackage.name}@${localPackage.version}`; if (tags.indexOf(tag) === -1) { utils.run(`git tag ${tag} -a -m "Release ${tag}"`); } }); lumino-2021.12.13/tsconfigdoc.json000066400000000000000000000030211415564225700166120ustar00rootroot00000000000000{ "$schema": "http://json.schemastore.org/tsconfig", "compilerOptions": { "allowSyntheticDefaultImports": true, "composite": true, "declaration": true, "esModuleInterop": true, "incremental": true, "jsx": "react", "module": "esnext", "moduleResolution": "node", "noEmitOnError": true, "noImplicitAny": true, "noUnusedLocals": true, "preserveWatchOutput": true, "resolveJsonModule": true, "sourceMap": true, "strictNullChecks": true, "target": "es2017", "types": ["@types/node"], "baseUrl": ".", "paths": { "@lumino/*": ["./packages/*/src"] } }, "exclude": ["**/tests/**", "examples/**"], "references": [ { "path": "./packages/algorithm" }, { "path": "./packages/application" }, { "path": "./packages/collections" }, { "path": "./packages/commands" }, { "path": "./packages/coreutils" }, { "path": "./packages/datagrid" }, { "path": "./packages/datastore" }, { "path": "./packages/disposable" }, { "path": "./packages/domutils" }, { "path": "./packages/dragdrop" }, { "path": "./packages/keyboard" }, { "path": "./packages/messaging" }, { "path": "./packages/polling" }, { "path": "./packages/properties" }, { "path": "./packages/signaling" }, { "path": "./packages/virtualdom" }, { "path": "./packages/widgets" } ] } lumino-2021.12.13/typedoc-theme/000077500000000000000000000000001415564225700161705ustar00rootroot00000000000000lumino-2021.12.13/typedoc-theme/partials/000077500000000000000000000000001415564225700200075ustar00rootroot00000000000000lumino-2021.12.13/typedoc-theme/partials/header.hbs000066400000000000000000000063371415564225700217460ustar00rootroot00000000000000
    Options
    All
    • Public
    • Public/Protected
    • All
    {{#unless settings.excludeExternals}} {{/unless}} {{#unless settings.excludeNotExported}} {{/unless}}
    Menu
      {{#with model}}{{> breadcrumb}}{{/with}}

    {{#compact}} {{model.kindString}}  {{model.name}} {{#if model.typeParameters}} < {{#each model.typeParameters}} {{#if @index}}, {{/if}} {{name}} {{/each}} > {{/if}} {{/compact}}

    lumino-2021.12.13/typedoc.js000066400000000000000000000017441415564225700154340ustar00rootroot00000000000000const fs = require('fs'); const path = require('path'); function getDirectories(path) { return fs.readdirSync(path).filter(function (file) { return fs.statSync(path + '/' + file).isDirectory(); }); } const packages = getDirectories(path.join(__dirname, 'packages')); const entryPoints = packages .flatMap(p => [`packages/${p}/src/index.ts`, `packages/${p}/src/index.tsx`]) .filter(function (path) { return fs.existsSync(path); }); const exclude = packages.flatMap(p => [`packages/${p}/tests`]); module.exports = { entryPoints, exclude, name: '@lumino', out: 'docs/source/api', // json: 'docs/source/api.json', readme: 'README.md', theme: 'typedoc-theme', tsconfig: 'tsconfigdoc.json' // theme: minimal, // excludePrivate: true, // excludeProtected: true, // excludeExternals: true, // hideGenerator: true // gitRevision: 'master', // 'sourcefile-url-prefix': `https://github.com/sinnerschrader/feature-hub/tree/${git.short()}/packages/`, }; lumino-2021.12.13/yarn.lock000066400000000000000000020567011415564225700152570ustar00rootroot00000000000000# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. # yarn lockfile v1 "@arcanis/slice-ansi@^1.0.2": version "1.0.2" resolved "https://registry.yarnpkg.com/@arcanis/slice-ansi/-/slice-ansi-1.0.2.tgz#35331e41a1062e3c53c01ad2ec1555c5c1959d8f" integrity sha512-lDL63z0W/L/WTgqrwVOuNyMAsTv+pvjybd21z9SWdStmQoXT59E/iVWwat3gYjcdTNBf6oHAMoyFm8dtjpXEYw== dependencies: grapheme-splitter "^1.0.4" "@babel/code-frame@^7.0.0": version "7.14.5" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.14.5.tgz#23b08d740e83f49c5e59945fbf1b43e80bbf4edb" integrity sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw== dependencies: "@babel/highlight" "^7.14.5" "@babel/helper-validator-identifier@^7.14.5": version "7.14.9" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.9.tgz#6654d171b2024f6d8ee151bf2509699919131d48" integrity sha512-pQYxPY0UP6IHISRitNe8bsijHex4TWZXi2HwKVsjPiltzlhse2znVcm9Ace510VT1kxIHjGJCZZQBX2gJDbo0g== "@babel/highlight@^7.14.5": version "7.14.5" resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.14.5.tgz#6861a52f03966405001f6aa534a01a24d99e8cd9" integrity sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg== dependencies: "@babel/helper-validator-identifier" "^7.14.5" chalk "^2.0.0" js-tokens "^4.0.0" "@deepcode/dcignore@^1.0.2": version "1.0.2" resolved "https://registry.yarnpkg.com/@deepcode/dcignore/-/dcignore-1.0.2.tgz#39e4a3df7dde8811925330506e4bb3fbf3c288d8" integrity sha512-DPgxtHuJwBORpqRkPXzzOT+uoPRVJmaN7LR+pmeL6DQM90kj6G6GFUH1i/YpRH8NbML8ZGEDwB9f9u4UwD2pzg== "@eslint/eslintrc@^0.2.1": version "0.2.2" resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.2.2.tgz#d01fc791e2fc33e88a29d6f3dc7e93d0cd784b76" integrity sha512-EfB5OHNYp1F4px/LI/FEnGylop7nOqkQ1LRzCM0KccA2U8tvV8w01KBv37LbO7nW4H+YhKyo2LcJhRwjjV17QQ== dependencies: ajv "^6.12.4" debug "^4.1.1" espree "^7.3.0" globals "^12.1.0" ignore "^4.0.6" import-fresh "^3.2.1" js-yaml "^3.13.1" lodash "^4.17.19" minimatch "^3.0.4" strip-json-comments "^3.1.1" "@hutson/parse-repository-url@^3.0.0": version "3.0.2" resolved "https://registry.yarnpkg.com/@hutson/parse-repository-url/-/parse-repository-url-3.0.2.tgz#98c23c950a3d9b6c8f0daed06da6c3af06981340" integrity sha512-H9XAx3hc0BQHY6l+IFSWHDySypcXsvsuLhgYLUGywmJ5pswRVQJUHpOsobnLYp2ZUaUlKiKDrgWWhosOwAEM8Q== "@jupyterlab/buildutils@^3.0.0": version "3.1.2" resolved "https://registry.yarnpkg.com/@jupyterlab/buildutils/-/buildutils-3.1.2.tgz#fd0b631658ac12654b7341a786f970b8958ae513" integrity sha512-p+M8ClJjb3xLVU974dichiDXQpRDRNYY6kw5ZVgSGqOl3eY0TVqfKpCbDj+xiCQDxX66MIqkYRALJdUkjhjPmw== dependencies: "@lumino/coreutils" "^1.5.3" "@yarnpkg/lockfile" "^1.1.0" child_process "~1.0.2" commander "~6.0.0" crypto "~1.0.1" dependency-graph "^0.9.0" fs-extra "^9.0.1" glob "~7.1.6" inquirer "^7.0.0" minimatch "~3.0.4" npm-cli-login "^0.1.1" os "~0.1.1" package-json "^6.5.0" prettier "~2.1.1" process "^0.11.10" semver "^7.3.2" sort-package-json "~1.44.0" typescript "~4.1.3" verdaccio "^5.1.1" "@lerna/add@4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@lerna/add/-/add-4.0.0.tgz#c36f57d132502a57b9e7058d1548b7a565ef183f" integrity sha512-cpmAH1iS3k8JBxNvnMqrGTTjbY/ZAiKa1ChJzFevMYY3eeqbvhsBKnBcxjRXtdrJ6bd3dCQM+ZtK+0i682Fhng== dependencies: "@lerna/bootstrap" "4.0.0" "@lerna/command" "4.0.0" "@lerna/filter-options" "4.0.0" "@lerna/npm-conf" "4.0.0" "@lerna/validation-error" "4.0.0" dedent "^0.7.0" npm-package-arg "^8.1.0" p-map "^4.0.0" pacote "^11.2.6" semver "^7.3.4" "@lerna/bootstrap@4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@lerna/bootstrap/-/bootstrap-4.0.0.tgz#5f5c5e2c6cfc8fcec50cb2fbe569a8c607101891" integrity sha512-RkS7UbeM2vu+kJnHzxNRCLvoOP9yGNgkzRdy4UV2hNalD7EP41bLvRVOwRYQ7fhc2QcbhnKNdOBihYRL0LcKtw== dependencies: "@lerna/command" "4.0.0" "@lerna/filter-options" "4.0.0" "@lerna/has-npm-version" "4.0.0" "@lerna/npm-install" "4.0.0" "@lerna/package-graph" "4.0.0" "@lerna/pulse-till-done" "4.0.0" "@lerna/rimraf-dir" "4.0.0" "@lerna/run-lifecycle" "4.0.0" "@lerna/run-topologically" "4.0.0" "@lerna/symlink-binary" "4.0.0" "@lerna/symlink-dependencies" "4.0.0" "@lerna/validation-error" "4.0.0" dedent "^0.7.0" get-port "^5.1.1" multimatch "^5.0.0" npm-package-arg "^8.1.0" npmlog "^4.1.2" p-map "^4.0.0" p-map-series "^2.1.0" p-waterfall "^2.1.1" read-package-tree "^5.3.1" semver "^7.3.4" "@lerna/changed@4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@lerna/changed/-/changed-4.0.0.tgz#b9fc76cea39b9292a6cd263f03eb57af85c9270b" integrity sha512-cD+KuPRp6qiPOD+BO6S6SN5cARspIaWSOqGBpGnYzLb4uWT8Vk4JzKyYtc8ym1DIwyoFXHosXt8+GDAgR8QrgQ== dependencies: "@lerna/collect-updates" "4.0.0" "@lerna/command" "4.0.0" "@lerna/listable" "4.0.0" "@lerna/output" "4.0.0" "@lerna/check-working-tree@4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@lerna/check-working-tree/-/check-working-tree-4.0.0.tgz#257e36a602c00142e76082a19358e3e1ae8dbd58" integrity sha512-/++bxM43jYJCshBiKP5cRlCTwSJdRSxVmcDAXM+1oUewlZJVSVlnks5eO0uLxokVFvLhHlC5kHMc7gbVFPHv6Q== dependencies: "@lerna/collect-uncommitted" "4.0.0" "@lerna/describe-ref" "4.0.0" "@lerna/validation-error" "4.0.0" "@lerna/child-process@4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@lerna/child-process/-/child-process-4.0.0.tgz#341b96a57dffbd9705646d316e231df6fa4df6e1" integrity sha512-XtCnmCT9eyVsUUHx6y/CTBYdV9g2Cr/VxyseTWBgfIur92/YKClfEtJTbOh94jRT62hlKLqSvux/UhxXVh613Q== dependencies: chalk "^4.1.0" execa "^5.0.0" strong-log-transformer "^2.1.0" "@lerna/clean@4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@lerna/clean/-/clean-4.0.0.tgz#8f778b6f2617aa2a936a6b5e085ae62498e57dc5" integrity sha512-uugG2iN9k45ITx2jtd8nEOoAtca8hNlDCUM0N3lFgU/b1mEQYAPRkqr1qs4FLRl/Y50ZJ41wUz1eazS+d/0osA== dependencies: "@lerna/command" "4.0.0" "@lerna/filter-options" "4.0.0" "@lerna/prompt" "4.0.0" "@lerna/pulse-till-done" "4.0.0" "@lerna/rimraf-dir" "4.0.0" p-map "^4.0.0" p-map-series "^2.1.0" p-waterfall "^2.1.1" "@lerna/cli@4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@lerna/cli/-/cli-4.0.0.tgz#8eabd334558836c1664df23f19acb95e98b5bbf3" integrity sha512-Neaw3GzFrwZiRZv2g7g6NwFjs3er1vhraIniEs0jjVLPMNC4eata0na3GfE5yibkM/9d3gZdmihhZdZ3EBdvYA== dependencies: "@lerna/global-options" "4.0.0" dedent "^0.7.0" npmlog "^4.1.2" yargs "^16.2.0" "@lerna/collect-uncommitted@4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@lerna/collect-uncommitted/-/collect-uncommitted-4.0.0.tgz#855cd64612969371cfc2453b90593053ff1ba779" integrity sha512-ufSTfHZzbx69YNj7KXQ3o66V4RC76ffOjwLX0q/ab//61bObJ41n03SiQEhSlmpP+gmFbTJ3/7pTe04AHX9m/g== dependencies: "@lerna/child-process" "4.0.0" chalk "^4.1.0" npmlog "^4.1.2" "@lerna/collect-updates@4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@lerna/collect-updates/-/collect-updates-4.0.0.tgz#8e208b1bafd98a372ff1177f7a5e288f6bea8041" integrity sha512-bnNGpaj4zuxsEkyaCZLka9s7nMs58uZoxrRIPJ+nrmrZYp1V5rrd+7/NYTuunOhY2ug1sTBvTAxj3NZQ+JKnOw== dependencies: "@lerna/child-process" "4.0.0" "@lerna/describe-ref" "4.0.0" minimatch "^3.0.4" npmlog "^4.1.2" slash "^3.0.0" "@lerna/command@4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@lerna/command/-/command-4.0.0.tgz#991c7971df8f5bf6ae6e42c808869a55361c1b98" integrity sha512-LM9g3rt5FsPNFqIHUeRwWXLNHJ5NKzOwmVKZ8anSp4e1SPrv2HNc1V02/9QyDDZK/w+5POXH5lxZUI1CHaOK/A== dependencies: "@lerna/child-process" "4.0.0" "@lerna/package-graph" "4.0.0" "@lerna/project" "4.0.0" "@lerna/validation-error" "4.0.0" "@lerna/write-log-file" "4.0.0" clone-deep "^4.0.1" dedent "^0.7.0" execa "^5.0.0" is-ci "^2.0.0" npmlog "^4.1.2" "@lerna/conventional-commits@4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@lerna/conventional-commits/-/conventional-commits-4.0.0.tgz#660fb2c7b718cb942ead70110df61f18c6f99750" integrity sha512-CSUQRjJHFrH8eBn7+wegZLV3OrNc0Y1FehYfYGhjLE2SIfpCL4bmfu/ViYuHh9YjwHaA+4SX6d3hR+xkeseKmw== dependencies: "@lerna/validation-error" "4.0.0" conventional-changelog-angular "^5.0.12" conventional-changelog-core "^4.2.2" conventional-recommended-bump "^6.1.0" fs-extra "^9.1.0" get-stream "^6.0.0" lodash.template "^4.5.0" npm-package-arg "^8.1.0" npmlog "^4.1.2" pify "^5.0.0" semver "^7.3.4" "@lerna/create-symlink@4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@lerna/create-symlink/-/create-symlink-4.0.0.tgz#8c5317ce5ae89f67825443bd7651bf4121786228" integrity sha512-I0phtKJJdafUiDwm7BBlEUOtogmu8+taxq6PtIrxZbllV9hWg59qkpuIsiFp+no7nfRVuaasNYHwNUhDAVQBig== dependencies: cmd-shim "^4.1.0" fs-extra "^9.1.0" npmlog "^4.1.2" "@lerna/create@4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@lerna/create/-/create-4.0.0.tgz#b6947e9b5dfb6530321952998948c3e63d64d730" integrity sha512-mVOB1niKByEUfxlbKTM1UNECWAjwUdiioIbRQZEeEabtjCL69r9rscIsjlGyhGWCfsdAG5wfq4t47nlDXdLLag== dependencies: "@lerna/child-process" "4.0.0" "@lerna/command" "4.0.0" "@lerna/npm-conf" "4.0.0" "@lerna/validation-error" "4.0.0" dedent "^0.7.0" fs-extra "^9.1.0" globby "^11.0.2" init-package-json "^2.0.2" npm-package-arg "^8.1.0" p-reduce "^2.1.0" pacote "^11.2.6" pify "^5.0.0" semver "^7.3.4" slash "^3.0.0" validate-npm-package-license "^3.0.4" validate-npm-package-name "^3.0.0" whatwg-url "^8.4.0" yargs-parser "20.2.4" "@lerna/describe-ref@4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@lerna/describe-ref/-/describe-ref-4.0.0.tgz#53c53b4ea65fdceffa072a62bfebe6772c45d9ec" integrity sha512-eTU5+xC4C5Gcgz+Ey4Qiw9nV2B4JJbMulsYJMW8QjGcGh8zudib7Sduj6urgZXUYNyhYpRs+teci9M2J8u+UvQ== dependencies: "@lerna/child-process" "4.0.0" npmlog "^4.1.2" "@lerna/diff@4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@lerna/diff/-/diff-4.0.0.tgz#6d3071817aaa4205a07bf77cfc6e932796d48b92" integrity sha512-jYPKprQVg41+MUMxx6cwtqsNm0Yxx9GDEwdiPLwcUTFx+/qKCEwifKNJ1oGIPBxyEHX2PFCOjkK39lHoj2qiag== dependencies: "@lerna/child-process" "4.0.0" "@lerna/command" "4.0.0" "@lerna/validation-error" "4.0.0" npmlog "^4.1.2" "@lerna/exec@4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@lerna/exec/-/exec-4.0.0.tgz#eb6cb95cb92d42590e9e2d628fcaf4719d4a8be6" integrity sha512-VGXtL/b/JfY84NB98VWZpIExfhLOzy0ozm/0XaS4a2SmkAJc5CeUfrhvHxxkxiTBLkU+iVQUyYEoAT0ulQ8PCw== dependencies: "@lerna/child-process" "4.0.0" "@lerna/command" "4.0.0" "@lerna/filter-options" "4.0.0" "@lerna/profiler" "4.0.0" "@lerna/run-topologically" "4.0.0" "@lerna/validation-error" "4.0.0" p-map "^4.0.0" "@lerna/filter-options@4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@lerna/filter-options/-/filter-options-4.0.0.tgz#ac94cc515d7fa3b47e2f7d74deddeabb1de5e9e6" integrity sha512-vV2ANOeZhOqM0rzXnYcFFCJ/kBWy/3OA58irXih9AMTAlQLymWAK0akWybl++sUJ4HB9Hx12TOqaXbYS2NM5uw== dependencies: "@lerna/collect-updates" "4.0.0" "@lerna/filter-packages" "4.0.0" dedent "^0.7.0" npmlog "^4.1.2" "@lerna/filter-packages@4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@lerna/filter-packages/-/filter-packages-4.0.0.tgz#b1f70d70e1de9cdd36a4e50caa0ac501f8d012f2" integrity sha512-+4AJIkK7iIiOaqCiVTYJxh/I9qikk4XjNQLhE3kixaqgMuHl1NQ99qXRR0OZqAWB9mh8Z1HA9bM5K1HZLBTOqA== dependencies: "@lerna/validation-error" "4.0.0" multimatch "^5.0.0" npmlog "^4.1.2" "@lerna/get-npm-exec-opts@4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@lerna/get-npm-exec-opts/-/get-npm-exec-opts-4.0.0.tgz#dc955be94a4ae75c374ef9bce91320887d34608f" integrity sha512-yvmkerU31CTWS2c7DvmAWmZVeclPBqI7gPVr5VATUKNWJ/zmVcU4PqbYoLu92I9Qc4gY1TuUplMNdNuZTSL7IQ== dependencies: npmlog "^4.1.2" "@lerna/get-packed@4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@lerna/get-packed/-/get-packed-4.0.0.tgz#0989d61624ac1f97e393bdad2137c49cd7a37823" integrity sha512-rfWONRsEIGyPJTxFzC8ECb3ZbsDXJbfqWYyeeQQDrJRPnEJErlltRLPLgC2QWbxFgFPsoDLeQmFHJnf0iDfd8w== dependencies: fs-extra "^9.1.0" ssri "^8.0.1" tar "^6.1.0" "@lerna/github-client@4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@lerna/github-client/-/github-client-4.0.0.tgz#2ced67721363ef70f8e12ffafce4410918f4a8a4" integrity sha512-2jhsldZtTKXYUBnOm23Lb0Fx8G4qfSXF9y7UpyUgWUj+YZYd+cFxSuorwQIgk5P4XXrtVhsUesIsli+BYSThiw== dependencies: "@lerna/child-process" "4.0.0" "@octokit/plugin-enterprise-rest" "^6.0.1" "@octokit/rest" "^18.1.0" git-url-parse "^11.4.4" npmlog "^4.1.2" "@lerna/gitlab-client@4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@lerna/gitlab-client/-/gitlab-client-4.0.0.tgz#00dad73379c7b38951d4b4ded043504c14e2b67d" integrity sha512-OMUpGSkeDWFf7BxGHlkbb35T7YHqVFCwBPSIR6wRsszY8PAzCYahtH3IaJzEJyUg6vmZsNl0FSr3pdA2skhxqA== dependencies: node-fetch "^2.6.1" npmlog "^4.1.2" whatwg-url "^8.4.0" "@lerna/global-options@4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@lerna/global-options/-/global-options-4.0.0.tgz#c7d8b0de6a01d8a845e2621ea89e7f60f18c6a5f" integrity sha512-TRMR8afAHxuYBHK7F++Ogop2a82xQjoGna1dvPOY6ltj/pEx59pdgcJfYcynYqMkFIk8bhLJJN9/ndIfX29FTQ== "@lerna/has-npm-version@4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@lerna/has-npm-version/-/has-npm-version-4.0.0.tgz#d3fc3292c545eb28bd493b36e6237cf0279f631c" integrity sha512-LQ3U6XFH8ZmLCsvsgq1zNDqka0Xzjq5ibVN+igAI5ccRWNaUsE/OcmsyMr50xAtNQMYMzmpw5GVLAivT2/YzCg== dependencies: "@lerna/child-process" "4.0.0" semver "^7.3.4" "@lerna/import@4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@lerna/import/-/import-4.0.0.tgz#bde656c4a451fa87ae41733ff8a8da60547c5465" integrity sha512-FaIhd+4aiBousKNqC7TX1Uhe97eNKf5/SC7c5WZANVWtC7aBWdmswwDt3usrzCNpj6/Wwr9EtEbYROzxKH8ffg== dependencies: "@lerna/child-process" "4.0.0" "@lerna/command" "4.0.0" "@lerna/prompt" "4.0.0" "@lerna/pulse-till-done" "4.0.0" "@lerna/validation-error" "4.0.0" dedent "^0.7.0" fs-extra "^9.1.0" p-map-series "^2.1.0" "@lerna/info@4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@lerna/info/-/info-4.0.0.tgz#b9fb0e479d60efe1623603958a831a88b1d7f1fc" integrity sha512-8Uboa12kaCSZEn4XRfPz5KU9XXoexSPS4oeYGj76s2UQb1O1GdnEyfjyNWoUl1KlJ2i/8nxUskpXIftoFYH0/Q== dependencies: "@lerna/command" "4.0.0" "@lerna/output" "4.0.0" envinfo "^7.7.4" "@lerna/init@4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@lerna/init/-/init-4.0.0.tgz#dadff67e6dfb981e8ccbe0e6a310e837962f6c7a" integrity sha512-wY6kygop0BCXupzWj5eLvTUqdR7vIAm0OgyV9WHpMYQGfs1V22jhztt8mtjCloD/O0nEe4tJhdG62XU5aYmPNQ== dependencies: "@lerna/child-process" "4.0.0" "@lerna/command" "4.0.0" fs-extra "^9.1.0" p-map "^4.0.0" write-json-file "^4.3.0" "@lerna/link@4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@lerna/link/-/link-4.0.0.tgz#c3a38aabd44279d714e90f2451e31b63f0fb65ba" integrity sha512-KlvPi7XTAcVOByfaLlOeYOfkkDcd+bejpHMCd1KcArcFTwijOwXOVi24DYomIeHvy6HsX/IUquJ4PPUJIeB4+w== dependencies: "@lerna/command" "4.0.0" "@lerna/package-graph" "4.0.0" "@lerna/symlink-dependencies" "4.0.0" p-map "^4.0.0" slash "^3.0.0" "@lerna/list@4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@lerna/list/-/list-4.0.0.tgz#24b4e6995bd73f81c556793fe502b847efd9d1d7" integrity sha512-L2B5m3P+U4Bif5PultR4TI+KtW+SArwq1i75QZ78mRYxPc0U/piau1DbLOmwrdqr99wzM49t0Dlvl6twd7GHFg== dependencies: "@lerna/command" "4.0.0" "@lerna/filter-options" "4.0.0" "@lerna/listable" "4.0.0" "@lerna/output" "4.0.0" "@lerna/listable@4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@lerna/listable/-/listable-4.0.0.tgz#d00d6cb4809b403f2b0374fc521a78e318b01214" integrity sha512-/rPOSDKsOHs5/PBLINZOkRIX1joOXUXEtyUs5DHLM8q6/RP668x/1lFhw6Dx7/U+L0+tbkpGtZ1Yt0LewCLgeQ== dependencies: "@lerna/query-graph" "4.0.0" chalk "^4.1.0" columnify "^1.5.4" "@lerna/log-packed@4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@lerna/log-packed/-/log-packed-4.0.0.tgz#95168fe2e26ac6a71e42f4be857519b77e57a09f" integrity sha512-+dpCiWbdzgMAtpajLToy9PO713IHoE6GV/aizXycAyA07QlqnkpaBNZ8DW84gHdM1j79TWockGJo9PybVhrrZQ== dependencies: byte-size "^7.0.0" columnify "^1.5.4" has-unicode "^2.0.1" npmlog "^4.1.2" "@lerna/npm-conf@4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@lerna/npm-conf/-/npm-conf-4.0.0.tgz#b259fd1e1cee2bf5402b236e770140ff9ade7fd2" integrity sha512-uS7H02yQNq3oejgjxAxqq/jhwGEE0W0ntr8vM3EfpCW1F/wZruwQw+7bleJQ9vUBjmdXST//tk8mXzr5+JXCfw== dependencies: config-chain "^1.1.12" pify "^5.0.0" "@lerna/npm-dist-tag@4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@lerna/npm-dist-tag/-/npm-dist-tag-4.0.0.tgz#d1e99b4eccd3414142f0548ad331bf2d53f3257a" integrity sha512-F20sg28FMYTgXqEQihgoqSfwmq+Id3zT23CnOwD+XQMPSy9IzyLf1fFVH319vXIw6NF6Pgs4JZN2Qty6/CQXGw== dependencies: "@lerna/otplease" "4.0.0" npm-package-arg "^8.1.0" npm-registry-fetch "^9.0.0" npmlog "^4.1.2" "@lerna/npm-install@4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@lerna/npm-install/-/npm-install-4.0.0.tgz#31180be3ab3b7d1818a1a0c206aec156b7094c78" integrity sha512-aKNxq2j3bCH3eXl3Fmu4D54s/YLL9WSwV8W7X2O25r98wzrO38AUN6AB9EtmAx+LV/SP15et7Yueg9vSaanRWg== dependencies: "@lerna/child-process" "4.0.0" "@lerna/get-npm-exec-opts" "4.0.0" fs-extra "^9.1.0" npm-package-arg "^8.1.0" npmlog "^4.1.2" signal-exit "^3.0.3" write-pkg "^4.0.0" "@lerna/npm-publish@4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@lerna/npm-publish/-/npm-publish-4.0.0.tgz#84eb62e876fe949ae1fd62c60804423dbc2c4472" integrity sha512-vQb7yAPRo5G5r77DRjHITc9piR9gvEKWrmfCH7wkfBnGWEqu7n8/4bFQ7lhnkujvc8RXOsYpvbMQkNfkYibD/w== dependencies: "@lerna/otplease" "4.0.0" "@lerna/run-lifecycle" "4.0.0" fs-extra "^9.1.0" libnpmpublish "^4.0.0" npm-package-arg "^8.1.0" npmlog "^4.1.2" pify "^5.0.0" read-package-json "^3.0.0" "@lerna/npm-run-script@4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@lerna/npm-run-script/-/npm-run-script-4.0.0.tgz#dfebf4f4601442e7c0b5214f9fb0d96c9350743b" integrity sha512-Jmyh9/IwXJjOXqKfIgtxi0bxi1pUeKe5bD3S81tkcy+kyng/GNj9WSqD5ZggoNP2NP//s4CLDAtUYLdP7CU9rA== dependencies: "@lerna/child-process" "4.0.0" "@lerna/get-npm-exec-opts" "4.0.0" npmlog "^4.1.2" "@lerna/otplease@4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@lerna/otplease/-/otplease-4.0.0.tgz#84972eb43448f8a1077435ba1c5e59233b725850" integrity sha512-Sgzbqdk1GH4psNiT6hk+BhjOfIr/5KhGBk86CEfHNJTk9BK4aZYyJD4lpDbDdMjIV4g03G7pYoqHzH765T4fxw== dependencies: "@lerna/prompt" "4.0.0" "@lerna/output@4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@lerna/output/-/output-4.0.0.tgz#b1d72215c0e35483e4f3e9994debc82c621851f2" integrity sha512-Un1sHtO1AD7buDQrpnaYTi2EG6sLF+KOPEAMxeUYG5qG3khTs2Zgzq5WE3dt2N/bKh7naESt20JjIW6tBELP0w== dependencies: npmlog "^4.1.2" "@lerna/pack-directory@4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@lerna/pack-directory/-/pack-directory-4.0.0.tgz#8b617db95d20792f043aaaa13a9ccc0e04cb4c74" integrity sha512-NJrmZNmBHS+5aM+T8N6FVbaKFScVqKlQFJNY2k7nsJ/uklNKsLLl6VhTQBPwMTbf6Tf7l6bcKzpy7aePuq9UiQ== dependencies: "@lerna/get-packed" "4.0.0" "@lerna/package" "4.0.0" "@lerna/run-lifecycle" "4.0.0" npm-packlist "^2.1.4" npmlog "^4.1.2" tar "^6.1.0" temp-write "^4.0.0" "@lerna/package-graph@4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@lerna/package-graph/-/package-graph-4.0.0.tgz#16a00253a8ac810f72041481cb46bcee8d8123dd" integrity sha512-QED2ZCTkfXMKFoTGoccwUzjHtZMSf3UKX14A4/kYyBms9xfFsesCZ6SLI5YeySEgcul8iuIWfQFZqRw+Qrjraw== dependencies: "@lerna/prerelease-id-from-version" "4.0.0" "@lerna/validation-error" "4.0.0" npm-package-arg "^8.1.0" npmlog "^4.1.2" semver "^7.3.4" "@lerna/package@4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@lerna/package/-/package-4.0.0.tgz#1b4c259c4bcff45c876ee1d591a043aacbc0d6b7" integrity sha512-l0M/izok6FlyyitxiQKr+gZLVFnvxRQdNhzmQ6nRnN9dvBJWn+IxxpM+cLqGACatTnyo9LDzNTOj2Db3+s0s8Q== dependencies: load-json-file "^6.2.0" npm-package-arg "^8.1.0" write-pkg "^4.0.0" "@lerna/prerelease-id-from-version@4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@lerna/prerelease-id-from-version/-/prerelease-id-from-version-4.0.0.tgz#c7e0676fcee1950d85630e108eddecdd5b48c916" integrity sha512-GQqguzETdsYRxOSmdFZ6zDBXDErIETWOqomLERRY54f4p+tk4aJjoVdd9xKwehC9TBfIFvlRbL1V9uQGHh1opg== dependencies: semver "^7.3.4" "@lerna/profiler@4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@lerna/profiler/-/profiler-4.0.0.tgz#8a53ab874522eae15d178402bff90a14071908e9" integrity sha512-/BaEbqnVh1LgW/+qz8wCuI+obzi5/vRE8nlhjPzdEzdmWmZXuCKyWSEzAyHOJWw1ntwMiww5dZHhFQABuoFz9Q== dependencies: fs-extra "^9.1.0" npmlog "^4.1.2" upath "^2.0.1" "@lerna/project@4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@lerna/project/-/project-4.0.0.tgz#ff84893935833533a74deff30c0e64ddb7f0ba6b" integrity sha512-o0MlVbDkD5qRPkFKlBZsXZjoNTWPyuL58564nSfZJ6JYNmgAptnWPB2dQlAc7HWRZkmnC2fCkEdoU+jioPavbg== dependencies: "@lerna/package" "4.0.0" "@lerna/validation-error" "4.0.0" cosmiconfig "^7.0.0" dedent "^0.7.0" dot-prop "^6.0.1" glob-parent "^5.1.1" globby "^11.0.2" load-json-file "^6.2.0" npmlog "^4.1.2" p-map "^4.0.0" resolve-from "^5.0.0" write-json-file "^4.3.0" "@lerna/prompt@4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@lerna/prompt/-/prompt-4.0.0.tgz#5ec69a803f3f0db0ad9f221dad64664d3daca41b" integrity sha512-4Ig46oCH1TH5M7YyTt53fT6TuaKMgqUUaqdgxvp6HP6jtdak6+amcsqB8YGz2eQnw/sdxunx84DfI9XpoLj4bQ== dependencies: inquirer "^7.3.3" npmlog "^4.1.2" "@lerna/publish@4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@lerna/publish/-/publish-4.0.0.tgz#f67011305adeba120066a3b6d984a5bb5fceef65" integrity sha512-K8jpqjHrChH22qtkytA5GRKIVFEtqBF6JWj1I8dWZtHs4Jywn8yB1jQ3BAMLhqmDJjWJtRck0KXhQQKzDK2UPg== dependencies: "@lerna/check-working-tree" "4.0.0" "@lerna/child-process" "4.0.0" "@lerna/collect-updates" "4.0.0" "@lerna/command" "4.0.0" "@lerna/describe-ref" "4.0.0" "@lerna/log-packed" "4.0.0" "@lerna/npm-conf" "4.0.0" "@lerna/npm-dist-tag" "4.0.0" "@lerna/npm-publish" "4.0.0" "@lerna/otplease" "4.0.0" "@lerna/output" "4.0.0" "@lerna/pack-directory" "4.0.0" "@lerna/prerelease-id-from-version" "4.0.0" "@lerna/prompt" "4.0.0" "@lerna/pulse-till-done" "4.0.0" "@lerna/run-lifecycle" "4.0.0" "@lerna/run-topologically" "4.0.0" "@lerna/validation-error" "4.0.0" "@lerna/version" "4.0.0" fs-extra "^9.1.0" libnpmaccess "^4.0.1" npm-package-arg "^8.1.0" npm-registry-fetch "^9.0.0" npmlog "^4.1.2" p-map "^4.0.0" p-pipe "^3.1.0" pacote "^11.2.6" semver "^7.3.4" "@lerna/pulse-till-done@4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@lerna/pulse-till-done/-/pulse-till-done-4.0.0.tgz#04bace7d483a8205c187b806bcd8be23d7bb80a3" integrity sha512-Frb4F7QGckaybRhbF7aosLsJ5e9WuH7h0KUkjlzSByVycxY91UZgaEIVjS2oN9wQLrheLMHl6SiFY0/Pvo0Cxg== dependencies: npmlog "^4.1.2" "@lerna/query-graph@4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@lerna/query-graph/-/query-graph-4.0.0.tgz#09dd1c819ac5ee3f38db23931143701f8a6eef63" integrity sha512-YlP6yI3tM4WbBmL9GCmNDoeQyzcyg1e4W96y/PKMZa5GbyUvkS2+Jc2kwPD+5KcXou3wQZxSPzR3Te5OenaDdg== dependencies: "@lerna/package-graph" "4.0.0" "@lerna/resolve-symlink@4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@lerna/resolve-symlink/-/resolve-symlink-4.0.0.tgz#6d006628a210c9b821964657a9e20a8c9a115e14" integrity sha512-RtX8VEUzqT+uLSCohx8zgmjc6zjyRlh6i/helxtZTMmc4+6O4FS9q5LJas2uGO2wKvBlhcD6siibGt7dIC3xZA== dependencies: fs-extra "^9.1.0" npmlog "^4.1.2" read-cmd-shim "^2.0.0" "@lerna/rimraf-dir@4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@lerna/rimraf-dir/-/rimraf-dir-4.0.0.tgz#2edf3b62d4eb0ef4e44e430f5844667d551ec25a" integrity sha512-QNH9ABWk9mcMJh2/muD9iYWBk1oQd40y6oH+f3wwmVGKYU5YJD//+zMiBI13jxZRtwBx0vmBZzkBkK1dR11cBg== dependencies: "@lerna/child-process" "4.0.0" npmlog "^4.1.2" path-exists "^4.0.0" rimraf "^3.0.2" "@lerna/run-lifecycle@4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@lerna/run-lifecycle/-/run-lifecycle-4.0.0.tgz#e648a46f9210a9bcd7c391df6844498cb5079334" integrity sha512-IwxxsajjCQQEJAeAaxF8QdEixfI7eLKNm4GHhXHrgBu185JcwScFZrj9Bs+PFKxwb+gNLR4iI5rpUdY8Y0UdGQ== dependencies: "@lerna/npm-conf" "4.0.0" npm-lifecycle "^3.1.5" npmlog "^4.1.2" "@lerna/run-topologically@4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@lerna/run-topologically/-/run-topologically-4.0.0.tgz#af846eeee1a09b0c2be0d1bfb5ef0f7b04bb1827" integrity sha512-EVZw9hGwo+5yp+VL94+NXRYisqgAlj0jWKWtAIynDCpghRxCE5GMO3xrQLmQgqkpUl9ZxQFpICgYv5DW4DksQA== dependencies: "@lerna/query-graph" "4.0.0" p-queue "^6.6.2" "@lerna/run@4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@lerna/run/-/run-4.0.0.tgz#4bc7fda055a729487897c23579694f6183c91262" integrity sha512-9giulCOzlMPzcZS/6Eov6pxE9gNTyaXk0Man+iCIdGJNMrCnW7Dme0Z229WWP/UoxDKg71F2tMsVVGDiRd8fFQ== dependencies: "@lerna/command" "4.0.0" "@lerna/filter-options" "4.0.0" "@lerna/npm-run-script" "4.0.0" "@lerna/output" "4.0.0" "@lerna/profiler" "4.0.0" "@lerna/run-topologically" "4.0.0" "@lerna/timer" "4.0.0" "@lerna/validation-error" "4.0.0" p-map "^4.0.0" "@lerna/symlink-binary@4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@lerna/symlink-binary/-/symlink-binary-4.0.0.tgz#21009f62d53a425f136cb4c1a32c6b2a0cc02d47" integrity sha512-zualodWC4q1QQc1pkz969hcFeWXOsVYZC5AWVtAPTDfLl+TwM7eG/O6oP+Rr3fFowspxo6b1TQ6sYfDV6HXNWA== dependencies: "@lerna/create-symlink" "4.0.0" "@lerna/package" "4.0.0" fs-extra "^9.1.0" p-map "^4.0.0" "@lerna/symlink-dependencies@4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@lerna/symlink-dependencies/-/symlink-dependencies-4.0.0.tgz#8910eca084ae062642d0490d8972cf2d98e9ebbd" integrity sha512-BABo0MjeUHNAe2FNGty1eantWp8u83BHSeIMPDxNq0MuW2K3CiQRaeWT3EGPAzXpGt0+hVzBrA6+OT0GPn7Yuw== dependencies: "@lerna/create-symlink" "4.0.0" "@lerna/resolve-symlink" "4.0.0" "@lerna/symlink-binary" "4.0.0" fs-extra "^9.1.0" p-map "^4.0.0" p-map-series "^2.1.0" "@lerna/timer@4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@lerna/timer/-/timer-4.0.0.tgz#a52e51bfcd39bfd768988049ace7b15c1fd7a6da" integrity sha512-WFsnlaE7SdOvjuyd05oKt8Leg3ENHICnvX3uYKKdByA+S3g+TCz38JsNs7OUZVt+ba63nC2nbXDlUnuT2Xbsfg== "@lerna/validation-error@4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@lerna/validation-error/-/validation-error-4.0.0.tgz#af9d62fe8304eaa2eb9a6ba1394f9aa807026d35" integrity sha512-1rBOM5/koiVWlRi3V6dB863E1YzJS8v41UtsHgMr6gB2ncJ2LsQtMKlJpi3voqcgh41H8UsPXR58RrrpPpufyw== dependencies: npmlog "^4.1.2" "@lerna/version@4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@lerna/version/-/version-4.0.0.tgz#532659ec6154d8a8789c5ab53878663e244e3228" integrity sha512-otUgiqs5W9zGWJZSCCMRV/2Zm2A9q9JwSDS7s/tlKq4mWCYriWo7+wsHEA/nPTMDyYyBO5oyZDj+3X50KDUzeA== dependencies: "@lerna/check-working-tree" "4.0.0" "@lerna/child-process" "4.0.0" "@lerna/collect-updates" "4.0.0" "@lerna/command" "4.0.0" "@lerna/conventional-commits" "4.0.0" "@lerna/github-client" "4.0.0" "@lerna/gitlab-client" "4.0.0" "@lerna/output" "4.0.0" "@lerna/prerelease-id-from-version" "4.0.0" "@lerna/prompt" "4.0.0" "@lerna/run-lifecycle" "4.0.0" "@lerna/run-topologically" "4.0.0" "@lerna/validation-error" "4.0.0" chalk "^4.1.0" dedent "^0.7.0" load-json-file "^6.2.0" minimatch "^3.0.4" npmlog "^4.1.2" p-map "^4.0.0" p-pipe "^3.1.0" p-reduce "^2.1.0" p-waterfall "^2.1.1" semver "^7.3.4" slash "^3.0.0" temp-write "^4.0.0" write-json-file "^4.3.0" "@lerna/write-log-file@4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@lerna/write-log-file/-/write-log-file-4.0.0.tgz#18221a38a6a307d6b0a5844dd592ad53fa27091e" integrity sha512-XRG5BloiArpXRakcnPHmEHJp+4AtnhRtpDIHSghmXD5EichI1uD73J7FgPp30mm2pDRq3FdqB0NbwSEsJ9xFQg== dependencies: npmlog "^4.1.2" write-file-atomic "^3.0.3" "@microsoft/api-extractor-model@7.13.4": version "7.13.4" resolved "https://registry.yarnpkg.com/@microsoft/api-extractor-model/-/api-extractor-model-7.13.4.tgz#bff4a52a35da5d9896650041d4f7a769c970da60" integrity sha512-NYaR3hJinh089/Gkee8fvmEFf9zKkoUvNxgkqUlKBCDXH2+Ou4tNDuL8G6zjhKBPicHkp2VcL8l7q9H6txUkjQ== dependencies: "@microsoft/tsdoc" "0.13.2" "@microsoft/tsdoc-config" "~0.15.2" "@rushstack/node-core-library" "3.39.1" "@microsoft/api-extractor@^7.6.0": version "7.18.4" resolved "https://registry.yarnpkg.com/@microsoft/api-extractor/-/api-extractor-7.18.4.tgz#2d7641b36d323b4ac710d838a972be7e4f14d32b" integrity sha512-Wx45VuIAu09Pk9Qwzt0I57OX31BaWO2r6+mfSXqYFsJjYTqwUkdFh92G1GKYgvuR9oF/ai7w10wrFpx5WZYbGg== dependencies: "@microsoft/api-extractor-model" "7.13.4" "@microsoft/tsdoc" "0.13.2" "@microsoft/tsdoc-config" "~0.15.2" "@rushstack/node-core-library" "3.39.1" "@rushstack/rig-package" "0.2.13" "@rushstack/ts-command-line" "4.8.1" colors "~1.2.1" lodash "~4.17.15" resolve "~1.17.0" semver "~7.3.0" source-map "~0.6.1" typescript "~4.3.5" "@microsoft/tsdoc-config@~0.15.2": version "0.15.2" resolved "https://registry.yarnpkg.com/@microsoft/tsdoc-config/-/tsdoc-config-0.15.2.tgz#eb353c93f3b62ab74bdc9ab6f4a82bcf80140f14" integrity sha512-mK19b2wJHSdNf8znXSMYVShAHktVr/ib0Ck2FA3lsVBSEhSI/TfXT7DJQkAYgcztTuwazGcg58ZjYdk0hTCVrA== dependencies: "@microsoft/tsdoc" "0.13.2" ajv "~6.12.6" jju "~1.4.0" resolve "~1.19.0" "@microsoft/tsdoc@0.13.2": version "0.13.2" resolved "https://registry.yarnpkg.com/@microsoft/tsdoc/-/tsdoc-0.13.2.tgz#3b0efb6d3903bd49edb073696f60e90df08efb26" integrity sha512-WrHvO8PDL8wd8T2+zBGKrMwVL5IyzR3ryWUsl0PXgEV0QHup4mTLi0QcATefGI6Gx9Anu7vthPyyyLpY0EpiQg== "@nodelib/fs.scandir@2.1.5": version "2.1.5" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== dependencies: "@nodelib/fs.stat" "2.0.5" run-parallel "^1.1.9" "@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": version "2.0.5" resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== "@nodelib/fs.walk@^1.2.3": version "1.2.8" resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== dependencies: "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" "@npmcli/ci-detect@^1.0.0": version "1.3.0" resolved "https://registry.yarnpkg.com/@npmcli/ci-detect/-/ci-detect-1.3.0.tgz#6c1d2c625fb6ef1b9dea85ad0a5afcbef85ef22a" integrity sha512-oN3y7FAROHhrAt7Rr7PnTSwrHrZVRTS2ZbyxeQwSSYD0ifwM3YNgQqbaRmjcWoPyq77MjchusjJDspbzMmip1Q== "@npmcli/git@^2.1.0": version "2.1.0" resolved "https://registry.yarnpkg.com/@npmcli/git/-/git-2.1.0.tgz#2fbd77e147530247d37f325930d457b3ebe894f6" integrity sha512-/hBFX/QG1b+N7PZBFs0bi+evgRZcK9nWBxQKZkGoXUT5hJSwl5c4d7y8/hm+NQZRPhQ67RzFaj5UM9YeyKoryw== dependencies: "@npmcli/promise-spawn" "^1.3.2" lru-cache "^6.0.0" mkdirp "^1.0.4" npm-pick-manifest "^6.1.1" promise-inflight "^1.0.1" promise-retry "^2.0.1" semver "^7.3.5" which "^2.0.2" "@npmcli/installed-package-contents@^1.0.6": version "1.0.7" resolved "https://registry.yarnpkg.com/@npmcli/installed-package-contents/-/installed-package-contents-1.0.7.tgz#ab7408c6147911b970a8abe261ce512232a3f4fa" integrity sha512-9rufe0wnJusCQoLpV9ZPKIVP55itrM5BxOXs10DmdbRfgWtHy1LDyskbwRnBghuB0PrF7pNPOqREVtpz4HqzKw== dependencies: npm-bundled "^1.1.1" npm-normalize-package-bin "^1.0.1" "@npmcli/move-file@^1.0.1": version "1.1.2" resolved "https://registry.yarnpkg.com/@npmcli/move-file/-/move-file-1.1.2.tgz#1a82c3e372f7cae9253eb66d72543d6b8685c674" integrity sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg== dependencies: mkdirp "^1.0.4" rimraf "^3.0.2" "@npmcli/node-gyp@^1.0.2": version "1.0.2" resolved "https://registry.yarnpkg.com/@npmcli/node-gyp/-/node-gyp-1.0.2.tgz#3cdc1f30e9736dbc417373ed803b42b1a0a29ede" integrity sha512-yrJUe6reVMpktcvagumoqD9r08fH1iRo01gn1u0zoCApa9lnZGEigVKUd2hzsCId4gdtkZZIVscLhNxMECKgRg== "@npmcli/promise-spawn@^1.2.0", "@npmcli/promise-spawn@^1.3.2": version "1.3.2" resolved "https://registry.yarnpkg.com/@npmcli/promise-spawn/-/promise-spawn-1.3.2.tgz#42d4e56a8e9274fba180dabc0aea6e38f29274f5" integrity sha512-QyAGYo/Fbj4MXeGdJcFzZ+FkDkomfRBrPM+9QYJSg+PxgAUL+LU3FneQk37rKR2/zjqkCV1BLHccX98wRXG3Sg== dependencies: infer-owner "^1.0.4" "@npmcli/run-script@^1.8.2": version "1.8.5" resolved "https://registry.yarnpkg.com/@npmcli/run-script/-/run-script-1.8.5.tgz#f250a0c5e1a08a792d775a315d0ff42fc3a51e1d" integrity sha512-NQspusBCpTjNwNRFMtz2C5MxoxyzlbuJ4YEhxAKrIonTiirKDtatsZictx9RgamQIx6+QuHMNmPl0wQdoESs9A== dependencies: "@npmcli/node-gyp" "^1.0.2" "@npmcli/promise-spawn" "^1.3.2" infer-owner "^1.0.4" node-gyp "^7.1.0" read-package-json-fast "^2.0.1" "@octetstream/promisify@2.0.2": version "2.0.2" resolved "https://registry.yarnpkg.com/@octetstream/promisify/-/promisify-2.0.2.tgz#29ac3bd7aefba646db670227f895d812c1a19615" integrity sha512-7XHoRB61hxsz8lBQrjC1tq/3OEIgpvGWg6DKAdwi7WRzruwkmsdwmOoUXbU4Dtd4RSOMDwed0SkP3y8UlMt1Bg== "@octokit/auth-token@^2.4.4": version "2.4.5" resolved "https://registry.yarnpkg.com/@octokit/auth-token/-/auth-token-2.4.5.tgz#568ccfb8cb46f36441fac094ce34f7a875b197f3" integrity sha512-BpGYsPgJt05M7/L/5FoE1PiAbdxXFZkX/3kDYcsvd1v6UhlnE5e96dTDr0ezX/EFwciQxf3cNV0loipsURU+WA== dependencies: "@octokit/types" "^6.0.3" "@octokit/core@^3.5.0": version "3.5.1" resolved "https://registry.yarnpkg.com/@octokit/core/-/core-3.5.1.tgz#8601ceeb1ec0e1b1b8217b960a413ed8e947809b" integrity sha512-omncwpLVxMP+GLpLPgeGJBF6IWJFjXDS5flY5VbppePYX9XehevbDykRH9PdCdvqt9TS5AOTiDide7h0qrkHjw== dependencies: "@octokit/auth-token" "^2.4.4" "@octokit/graphql" "^4.5.8" "@octokit/request" "^5.6.0" "@octokit/request-error" "^2.0.5" "@octokit/types" "^6.0.3" before-after-hook "^2.2.0" universal-user-agent "^6.0.0" "@octokit/endpoint@^6.0.1": version "6.0.12" resolved "https://registry.yarnpkg.com/@octokit/endpoint/-/endpoint-6.0.12.tgz#3b4d47a4b0e79b1027fb8d75d4221928b2d05658" integrity sha512-lF3puPwkQWGfkMClXb4k/eUT/nZKQfxinRWJrdZaJO85Dqwo/G0yOC434Jr2ojwafWJMYqFGFa5ms4jJUgujdA== dependencies: "@octokit/types" "^6.0.3" is-plain-object "^5.0.0" universal-user-agent "^6.0.0" "@octokit/graphql@^4.5.8": version "4.6.4" resolved "https://registry.yarnpkg.com/@octokit/graphql/-/graphql-4.6.4.tgz#0c3f5bed440822182e972317122acb65d311a5ed" integrity sha512-SWTdXsVheRmlotWNjKzPOb6Js6tjSqA2a8z9+glDJng0Aqjzti8MEWOtuT8ZSu6wHnci7LZNuarE87+WJBG4vg== dependencies: "@octokit/request" "^5.6.0" "@octokit/types" "^6.0.3" universal-user-agent "^6.0.0" "@octokit/openapi-types@^9.4.0": version "9.4.0" resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-9.4.0.tgz#31a76fb4c0f2e15af300edd880cedf4f75be212b" integrity sha512-rKRkXikOJgDNImPl49IJuECLVXjj+t4qOXHhl8SBjMQCGGp1w4m5Ud/0kfdUx+zCpTvBN8vaOUDF4nnboZoOtQ== "@octokit/plugin-enterprise-rest@^6.0.1": version "6.0.1" resolved "https://registry.yarnpkg.com/@octokit/plugin-enterprise-rest/-/plugin-enterprise-rest-6.0.1.tgz#e07896739618dab8da7d4077c658003775f95437" integrity sha512-93uGjlhUD+iNg1iWhUENAtJata6w5nE+V4urXOAlIXdco6xNZtUSfYY8dzp3Udy74aqO/B5UZL80x/YMa5PKRw== "@octokit/plugin-paginate-rest@^2.6.2": version "2.15.0" resolved "https://registry.yarnpkg.com/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.15.0.tgz#9c956c3710b2bd786eb3814eaf5a2b17392c150d" integrity sha512-/vjcb0w6ggVRtsb8OJBcRR9oEm+fpdo8RJk45khaWw/W0c8rlB2TLCLyZt/knmC17NkX7T9XdyQeEY7OHLSV1g== dependencies: "@octokit/types" "^6.23.0" "@octokit/plugin-request-log@^1.0.2": version "1.0.4" resolved "https://registry.yarnpkg.com/@octokit/plugin-request-log/-/plugin-request-log-1.0.4.tgz#5e50ed7083a613816b1e4a28aeec5fb7f1462e85" integrity sha512-mLUsMkgP7K/cnFEw07kWqXGF5LKrOkD+lhCrKvPHXWDywAwuDUeDwWBpc69XK3pNX0uKiVt8g5z96PJ6z9xCFA== "@octokit/plugin-rest-endpoint-methods@5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.7.0.tgz#80b69452c17597738d4692c79829b72d9e72ccec" integrity sha512-G7sgccWRYQMwcHJXkDY/sDxbXeKiZkFQqUtzBCwmrzCNj2GQf3VygQ4T/BFL2crLVpIbenkE/c0ErhYOte2MPw== dependencies: "@octokit/types" "^6.24.0" deprecation "^2.3.1" "@octokit/request-error@^2.0.5", "@octokit/request-error@^2.1.0": version "2.1.0" resolved "https://registry.yarnpkg.com/@octokit/request-error/-/request-error-2.1.0.tgz#9e150357831bfc788d13a4fd4b1913d60c74d677" integrity sha512-1VIvgXxs9WHSjicsRwq8PlR2LR2x6DwsJAaFgzdi0JfJoGSO8mYI/cHJQ+9FbN21aa+DrgNLnwObmyeSC8Rmpg== dependencies: "@octokit/types" "^6.0.3" deprecation "^2.0.0" once "^1.4.0" "@octokit/request@^5.6.0": version "5.6.0" resolved "https://registry.yarnpkg.com/@octokit/request/-/request-5.6.0.tgz#6084861b6e4fa21dc40c8e2a739ec5eff597e672" integrity sha512-4cPp/N+NqmaGQwbh3vUsYqokQIzt7VjsgTYVXiwpUP2pxd5YiZB2XuTedbb0SPtv9XS7nzAKjAuQxmY8/aZkiA== dependencies: "@octokit/endpoint" "^6.0.1" "@octokit/request-error" "^2.1.0" "@octokit/types" "^6.16.1" is-plain-object "^5.0.0" node-fetch "^2.6.1" universal-user-agent "^6.0.0" "@octokit/rest@^18.1.0": version "18.9.0" resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-18.9.0.tgz#e5cc23fa199a2bdeea9efbe6096f81d7d6156fe9" integrity sha512-VrmrE8gjpuOoDAGjrQq2j9ZhOE6LxaqxaQg0yMrrEnnQZy2ZcAnr5qbVfKsMF0up/48PRV/VFS/2GSMhA7nTdA== dependencies: "@octokit/core" "^3.5.0" "@octokit/plugin-paginate-rest" "^2.6.2" "@octokit/plugin-request-log" "^1.0.2" "@octokit/plugin-rest-endpoint-methods" "5.7.0" "@octokit/types@^6.0.3", "@octokit/types@^6.16.1", "@octokit/types@^6.23.0", "@octokit/types@^6.24.0": version "6.24.0" resolved "https://registry.yarnpkg.com/@octokit/types/-/types-6.24.0.tgz#d7858ceae8ac29256da85dcfcb9acbae28e6ba22" integrity sha512-MfEimJeQ8AV1T2nI5kOfHqsqPHaAnG0Dw3MVoHSEsEq6iLKx2N91o+k2uAgXhPYeSE76LVBqjgTShnFFgNwe0A== dependencies: "@octokit/openapi-types" "^9.4.0" "@open-policy-agent/opa-wasm@^1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@open-policy-agent/opa-wasm/-/opa-wasm-1.2.0.tgz#481b766093f70b00efefbee1e4192f375fd34ca2" integrity sha512-CtUBTnzvDrT0NASa8IuGQTxFGgt2vxbLnMYuTA+uDFxOcA4uK4mGFgrhHJtxUZnWHiwemOvKKSY3BMCo7qiAsQ== dependencies: sprintf-js "^1.1.2" utf8 "^3.0.0" "@rollup/pluginutils@^3.0.9": version "3.1.0" resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-3.1.0.tgz#706b4524ee6dc8b103b3c995533e5ad680c02b9b" integrity sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg== dependencies: "@types/estree" "0.0.39" estree-walker "^1.0.1" picomatch "^2.2.2" "@rushstack/node-core-library@3.39.1": version "3.39.1" resolved "https://registry.yarnpkg.com/@rushstack/node-core-library/-/node-core-library-3.39.1.tgz#dd1dc270e3035ac4de270f0ca80c25724ce19cc7" integrity sha512-HHgMEHZTXQ3NjpQzWd5+fSt2Eod9yFwj6qBPbaeaNtDNkOL8wbLoxVimQNtcH0Qhn4wxF5u2NTDNFsxf2yd1jw== dependencies: "@types/node" "10.17.13" colors "~1.2.1" fs-extra "~7.0.1" import-lazy "~4.0.0" jju "~1.4.0" resolve "~1.17.0" semver "~7.3.0" timsort "~0.3.0" z-schema "~3.18.3" "@rushstack/rig-package@0.2.13": version "0.2.13" resolved "https://registry.yarnpkg.com/@rushstack/rig-package/-/rig-package-0.2.13.tgz#418f0aeb4c9b33bd8bd2547759fc0ae91fd970c7" integrity sha512-qQMAFKvfb2ooaWU9DrGIK9d8QfyHy/HiuITJbWenlKgzcDXQvQgEduk57YF4Y7LLasDJ5ZzLaaXwlfX8qCRe5Q== dependencies: resolve "~1.17.0" strip-json-comments "~3.1.1" "@rushstack/ts-command-line@4.8.1": version "4.8.1" resolved "https://registry.yarnpkg.com/@rushstack/ts-command-line/-/ts-command-line-4.8.1.tgz#c233a0226112338e58e7e4fd219247b4e7cec883" integrity sha512-rmxvYdCNRbyRs+DYAPye3g6lkCkWHleqO40K8UPvUAzFqEuj6+YCVssBiOmrUDCoM5gaegSNT0wFDYhz24DWtw== dependencies: "@types/argparse" "1.0.38" argparse "~1.0.9" colors "~1.2.1" string-argv "~0.3.1" "@sindresorhus/is@^0.14.0": version "0.14.0" resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea" integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ== "@sindresorhus/is@^4.0.0": version "4.0.1" resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-4.0.1.tgz#d26729db850fa327b7cacc5522252194404226f5" integrity sha512-Qm9hBEBu18wt1PO2flE7LPb30BHMQt1eQgbV76YntdNk73XZGpn3izvGTYxbGgzXKgbCjiia0uxTd3aTNQrY/g== "@snyk/child-process@^0.3.1": version "0.3.1" resolved "https://registry.yarnpkg.com/@snyk/child-process/-/child-process-0.3.1.tgz#a5f713216627c24bec82f1a2f882b01323a524a2" integrity sha512-iJn0ENqf3xnYQdG/j9+pdqQPTrXCNXWU3I/AXGFx6Zr8IRBqBr5vXGTuwIJBYkq7s6bfIfz5AjUgPShCzeqn0w== dependencies: debug "^4.1.1" source-map-support "^0.5.16" tslib "^1.10.0" "@snyk/cli-interface@2.11.0": version "2.11.0" resolved "https://registry.yarnpkg.com/@snyk/cli-interface/-/cli-interface-2.11.0.tgz#9df68c8cd54de5dff69f0ab797a188541d9c8965" integrity sha512-T3xfDqrEFKclHGdJx4/5+D5F7e76/99f33guE4RTlVITBhy7VVnjz4t/NDr3UYqcC0MgAmiC4bSVYHnlshuwJw== dependencies: "@types/graphlib" "^2" "@snyk/cli-interface@^2.0.3", "@snyk/cli-interface@^2.11.0", "@snyk/cli-interface@^2.9.1", "@snyk/cli-interface@^2.9.2": version "2.11.1" resolved "https://registry.yarnpkg.com/@snyk/cli-interface/-/cli-interface-2.11.1.tgz#42c78584ab3fcda2729a0eab457505393eb6c7f0" integrity sha512-fm1tSVC8RwFelUZCKRAsSk4gPP9UjDfd3nZR/1Jgr9xw2xO79ngm1L3YApdHTpsNdCCw+nd94lnCsqLZDto2aQ== dependencies: "@types/graphlib" "^2" "@snyk/cloud-config-parser@^1.9.2": version "1.10.1" resolved "https://registry.yarnpkg.com/@snyk/cloud-config-parser/-/cloud-config-parser-1.10.1.tgz#982dcc4689efb25d2b9fd9b5bef1927b1f6d5247" integrity sha512-boqO3H4zkGo+Q2C7qyG2l/sQX80ZRSOlPCiRtgN9Xa7u9fM+qFGOaFOgNWfZZtU0wLBy2yDs5ipzdfqvp0ZEjg== dependencies: esprima "^4.0.1" tslib "^1.10.0" yaml-js "^0.3.0" "@snyk/cocoapods-lockfile-parser@3.6.2": version "3.6.2" resolved "https://registry.yarnpkg.com/@snyk/cocoapods-lockfile-parser/-/cocoapods-lockfile-parser-3.6.2.tgz#803ae9466f408c48ba7c5a8ec51b9dbac6f633b3" integrity sha512-ca2JKOnSRzYHJkhOB9gYmdRZHmd02b/uBd/S0D5W+L9nIMS7sUBV5jfhKwVgrYPIpVNIc0XCI9rxK4TfkQRpiA== dependencies: "@snyk/dep-graph" "^1.23.1" "@types/js-yaml" "^3.12.1" js-yaml "^3.13.1" tslib "^1.10.0" "@snyk/code-client@4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@snyk/code-client/-/code-client-4.0.0.tgz#e883deff79e3bdda2a163eb143aaf7f2c4029c50" integrity sha512-x6ZsL6jBz6rDSg1cFVnaqg4fVvQEerRaATem7aeq7l+HiPb8VnorJbq/61MlTgO7gWnA1WijzmzfJK5kAUpO1A== dependencies: "@deepcode/dcignore" "^1.0.2" "@snyk/fast-glob" "^3.2.6-patch" "@types/flat-cache" "^2.0.0" "@types/lodash.chunk" "^4.2.6" "@types/lodash.omit" "^4.5.6" "@types/lodash.pick" "^4.4.6" "@types/lodash.union" "^4.6.6" "@types/sarif" "^2.1.3" "@types/uuid" "^8.3.0" ignore "^5.1.8" lodash.chunk "^4.2.0" lodash.omit "^4.5.0" lodash.pick "^4.4.0" lodash.union "^4.6.0" multimatch "^5.0.0" needle "^2.8.0" queue "^6.0.1" uuid "^8.3.2" "@snyk/composer-lockfile-parser@^1.4.1": version "1.4.1" resolved "https://registry.yarnpkg.com/@snyk/composer-lockfile-parser/-/composer-lockfile-parser-1.4.1.tgz#2f7c93ad367520322b16d9490a208fec08445e0e" integrity sha512-wNANv235j95NFsQuODIXCiQZ9kcyg9fz92Kg1zoGvaP3kN/ma7fgCnvQL/dyml6iouQJR5aZovjhrrfEFoKtiQ== dependencies: lodash.findkey "^4.6.0" lodash.get "^4.4.2" lodash.invert "^4.3.0" lodash.isempty "^4.4.0" "@snyk/dep-graph@^1.19.3", "@snyk/dep-graph@^1.21.0", "@snyk/dep-graph@^1.23.0", "@snyk/dep-graph@^1.23.1", "@snyk/dep-graph@^1.27.1", "@snyk/dep-graph@^1.28.0": version "1.28.1" resolved "https://registry.yarnpkg.com/@snyk/dep-graph/-/dep-graph-1.28.1.tgz#dab0ce1974a93a2373aff73dde614c184148e832" integrity sha512-ti5fPYivhBGCJ7rZGznMX2UJE1M5lR811WvVyBWTRJwLYVFYkhxRXKfgZUXEB0tq8vpo3V7tm3syrBd5TLPIMA== dependencies: event-loop-spinner "^2.1.0" lodash.clone "^4.5.0" lodash.constant "^3.0.0" lodash.filter "^4.6.0" lodash.foreach "^4.5.0" lodash.isempty "^4.4.0" lodash.isequal "^4.5.0" lodash.isfunction "^3.0.9" lodash.isundefined "^3.0.1" lodash.keys "^4.2.0" lodash.map "^4.6.0" lodash.reduce "^4.6.0" lodash.size "^4.2.0" lodash.transform "^4.6.0" lodash.union "^4.6.0" lodash.values "^4.3.0" object-hash "^2.0.3" semver "^7.0.0" tslib "^1.13.0" "@snyk/docker-registry-v2-client@^2.3.0": version "2.3.0" resolved "https://registry.yarnpkg.com/@snyk/docker-registry-v2-client/-/docker-registry-v2-client-2.3.0.tgz#2a613cfdc88ecde3a9737340eb91d74e4ea6a5fa" integrity sha512-VYQe/1SuIdQ8C7bA6nzfcEeafsqG1cHaZDFaIt1uYGwI1TI0OWzUIvGRkfgkMkwFBVLRqS1hFczSoxGTT7OMfA== dependencies: needle "^2.5.0" parse-link-header "^1.0.1" tslib "^1.10.0" "@snyk/fast-glob@^3.2.6-patch": version "3.2.6-patch" resolved "https://registry.yarnpkg.com/@snyk/fast-glob/-/fast-glob-3.2.6-patch.tgz#a0866bedb17f95255e4050dad08daeaff0f4caa8" integrity sha512-E/Pfdze/WFfxwyuTFcfhQN1SwyUsc43yuCoW63RVBCaxTD6OzhVD2Pvc/Sy7BjiWUfmelzyKkIBpoow8zZX7Zg== dependencies: "@nodelib/fs.stat" "^2.0.2" "@nodelib/fs.walk" "^1.2.3" "@snyk/glob-parent" "^5.1.2-patch.1" merge2 "^1.3.0" micromatch "^4.0.2" picomatch "^2.2.1" "@snyk/fix-pipenv-pipfile@0.5.4": version "0.5.4" resolved "https://registry.yarnpkg.com/@snyk/fix-pipenv-pipfile/-/fix-pipenv-pipfile-0.5.4.tgz#c95f18fb4dbd76d247746fbb1ef366694695af4a" integrity sha512-n1Sg21htJG+gqw2Q9JYsIFxT5IDtELifbQZheiLhOl8sgfoD6oCNyf07IwUqzPrW2wRpmCrTUtuO08+KGnzJpg== dependencies: "@snyk/child-process" "^0.3.1" bottleneck "2.19.5" debug "4.3.1" tslib "^1.10.0" "@snyk/fix-poetry@0.7.2": version "0.7.2" resolved "https://registry.yarnpkg.com/@snyk/fix-poetry/-/fix-poetry-0.7.2.tgz#17d672a296cf2e4952d0a981e59b3566806838f5" integrity sha512-abBdeOb326exreUDnCkELw9GVOR7qdUQmX0sKyE1baz3i9WaWI5Wy0bWHqjw9oGE5ZP8Ki/xtzkHU2W57D++Zg== dependencies: "@snyk/child-process" "^0.3.1" bottleneck "2.19.5" debug "4.3.1" tslib "^1.10.0" "@snyk/fix@1.650.0": version "1.650.0" resolved "https://registry.yarnpkg.com/@snyk/fix/-/fix-1.650.0.tgz#c6b2b9863a624d7a58e305f457b644ccb2b55ce3" integrity sha512-qzwb0+DMrjxTsBXAcC74gb2X17925SapCBNo6DybLbOZXNJ6IFvtdN5ucd2FguEojfVyB4HVqkpbawc0bbfGtA== dependencies: "@snyk/dep-graph" "^1.21.0" "@snyk/fix-pipenv-pipfile" "0.5.4" "@snyk/fix-poetry" "0.7.2" chalk "4.1.1" debug "^4.3.1" lodash.groupby "4.6.0" lodash.sortby "^4.7.0" ora "5.4.0" p-map "^4.0.0" strip-ansi "6.0.0" toml "3.0.0" "@snyk/gemfile@1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@snyk/gemfile/-/gemfile-1.2.0.tgz#919857944973cce74c650e5428aaf11bcd5c0457" integrity sha512-nI7ELxukf7pT4/VraL4iabtNNMz8mUo7EXlqCFld8O5z6mIMLX9llps24iPpaIZOwArkY3FWA+4t+ixyvtTSIA== "@snyk/glob-parent@^5.1.2-patch.1": version "5.1.2-patch.1" resolved "https://registry.yarnpkg.com/@snyk/glob-parent/-/glob-parent-5.1.2-patch.1.tgz#87733b4ab282043fa7915200bc94cb391df6d44f" integrity sha512-OkUPdHgxIWKAAzceG1nraNA0kgI+eS0I9wph8tll9UL0slD2mIWSj4mAqroGovaEXm8nHedoUfuDRGEb6wnzCQ== dependencies: is-glob "^4.0.1" "@snyk/graphlib@2.1.9-patch.3", "@snyk/graphlib@^2.1.9-patch.3": version "2.1.9-patch.3" resolved "https://registry.yarnpkg.com/@snyk/graphlib/-/graphlib-2.1.9-patch.3.tgz#b8edb2335af1978db7f3cb1f28f5d562960acf23" integrity sha512-bBY9b9ulfLj0v2Eer0yFYa3syVeIxVKl2EpxSrsVeT4mjA0CltZyHsF0JjoaGXP27nItTdJS5uVsj1NA+3aE+Q== dependencies: lodash.clone "^4.5.0" lodash.constant "^3.0.0" lodash.filter "^4.6.0" lodash.foreach "^4.5.0" lodash.has "^4.5.2" lodash.isempty "^4.4.0" lodash.isfunction "^3.0.9" lodash.isundefined "^3.0.1" lodash.keys "^4.2.0" lodash.map "^4.6.0" lodash.reduce "^4.6.0" lodash.size "^4.2.0" lodash.transform "^4.6.0" lodash.union "^4.6.0" lodash.values "^4.3.0" "@snyk/inquirer@^7.3.3-patch": version "7.3.3-patch" resolved "https://registry.yarnpkg.com/@snyk/inquirer/-/inquirer-7.3.3-patch.tgz#ef84d531724c53b755e8dd454e1a3c2ccdcfc0bf" integrity sha512-aWiQSOacH2lOpJ1ard9ErABcH4tdJogdr+mg1U67iZJOPO9n2gFgAwz1TQJDyPkv4/A5mh4hT2rg03Uq+KBn2Q== dependencies: ansi-escapes "^4.2.1" chalk "^4.1.0" cli-cursor "^3.1.0" cli-width "^3.0.0" external-editor "^3.0.3" figures "^3.0.0" lodash.assign "^4.2.0" lodash.assignin "^4.2.0" lodash.clone "^4.5.0" lodash.defaults "^4.2.0" lodash.filter "^4.6.0" lodash.find "^4.6.0" lodash.findindex "^4.6.0" lodash.flatten "^4.4.0" lodash.isboolean "^3.0.3" lodash.isfunction "^3.0.9" lodash.isnumber "^3.0.3" lodash.isplainobject "^4.0.6" lodash.isstring "^4.0.1" lodash.last "^3.0.0" lodash.map "^4.6.0" lodash.omit "^4.5.0" lodash.set "^4.3.2" lodash.sum "^4.0.2" lodash.uniq "^4.5.0" mute-stream "0.0.8" run-async "^2.4.0" rxjs "^6.6.0" string-width "^4.1.0" strip-ansi "^6.0.0" through "^2.3.6" "@snyk/java-call-graph-builder@1.23.1": version "1.23.1" resolved "https://registry.yarnpkg.com/@snyk/java-call-graph-builder/-/java-call-graph-builder-1.23.1.tgz#ec8ed3333567fb634f167e6d0e466df8b98ab905" integrity sha512-mm6EI/BXFYq8boOHKs61j0R1n3JPsvwxlBsaO35cGFu9fTQaFRsBJdenKW41uJuLX+aFOC4zascbJDNfeE5THQ== dependencies: "@snyk/graphlib" "2.1.9-patch.3" ci-info "^2.0.0" debug "^4.1.1" glob "^7.1.6" jszip "^3.7.0" needle "^2.3.3" progress "^2.0.3" snyk-config "^4.0.0-rc.2" source-map-support "^0.5.7" temp-dir "^2.0.0" tmp "^0.2.1" tslib "^1.9.3" xml-js "^1.6.11" "@snyk/mix-parser@^1.1.1": version "1.3.2" resolved "https://registry.yarnpkg.com/@snyk/mix-parser/-/mix-parser-1.3.2.tgz#930de1d9c3a91e20660751f78c3e6f6a88ac5b2b" integrity sha512-0Aq9vcgmjH0d9Gk5q0k6l4ZOvSHPf6/BCQGDVOpKp0hwOkXWnpDOLLPxL+uBCktuH9zTYQFB0aTk91kQImZqmA== dependencies: "@snyk/dep-graph" "^1.28.0" tslib "^2.0.0" "@snyk/rpm-parser@^2.0.0": version "2.2.1" resolved "https://registry.yarnpkg.com/@snyk/rpm-parser/-/rpm-parser-2.2.1.tgz#b61ccf5478684b203576bd2be68de434ccbb0069" integrity sha512-OAON0bPf3c5fgM/GK9DX0aZErB6SnuRyYlPH0rqI1TXGsKrYnVELhaE6ctNbEfPTQuY9r6q0vM+UYDaFM/YliA== dependencies: event-loop-spinner "^2.0.0" "@snyk/snyk-cocoapods-plugin@2.5.2": version "2.5.2" resolved "https://registry.yarnpkg.com/@snyk/snyk-cocoapods-plugin/-/snyk-cocoapods-plugin-2.5.2.tgz#cd724fcd637cb3af76187bf7254819b6079489f6" integrity sha512-WHhnwyoGOhjFOjBXqUfszD84SErrtjHjium/4xFbqKpEE+yuwxs8OwV/S29BtxhYiGtjpD1azv5QtH30VUMl0A== dependencies: "@snyk/cli-interface" "^2.11.0" "@snyk/cocoapods-lockfile-parser" "3.6.2" "@snyk/dep-graph" "^1.23.1" source-map-support "^0.5.7" tslib "^2.0.0" "@snyk/snyk-docker-pull@^3.6.3": version "3.7.0" resolved "https://registry.yarnpkg.com/@snyk/snyk-docker-pull/-/snyk-docker-pull-3.7.0.tgz#a7fa3987d8a4502833b9e485ee46564843550e0b" integrity sha512-YRNysIPXmVPrP6+Gn8aG8T414r4GiSbxBP2R8CMXgBWFOdAPBoEoFjs7StjBfaVL1p0xl01AudgDnd42HDK9PA== dependencies: "@snyk/docker-registry-v2-client" "^2.3.0" child-process "^1.0.2" tar-stream "^2.2.0" tmp "^0.2.1" "@snyk/snyk-hex-plugin@1.1.4": version "1.1.4" resolved "https://registry.yarnpkg.com/@snyk/snyk-hex-plugin/-/snyk-hex-plugin-1.1.4.tgz#4a5b1684cecc1a557ec1a9f5f8646683ae89f0da" integrity sha512-kLfFGckSmyKe667UGPyWzR/H7/Trkt4fD8O/ktElOx1zWgmivpLm0Symb4RCfEmz9irWv+N6zIKRrfSNdytcPQ== dependencies: "@snyk/dep-graph" "^1.28.0" "@snyk/mix-parser" "^1.1.1" debug "^4.3.1" tmp "^0.0.33" tslib "^2.0.0" upath "2.0.1" "@szmarczak/http-timer@^1.1.2": version "1.1.2" resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-1.1.2.tgz#b1665e2c461a2cd92f4c1bbf50d5454de0d4b421" integrity sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA== dependencies: defer-to-connect "^1.0.1" "@szmarczak/http-timer@^4.0.5": version "4.0.6" resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-4.0.6.tgz#b4a914bb62e7c272d4e5989fe4440f812ab1d807" integrity sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w== dependencies: defer-to-connect "^2.0.0" "@tootallnate/once@1": version "1.1.2" resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== "@types/argparse@1.0.38": version "1.0.38" resolved "https://registry.yarnpkg.com/@types/argparse/-/argparse-1.0.38.tgz#a81fd8606d481f873a3800c6ebae4f1d768a56a9" integrity sha512-ebDJ9b0e702Yr7pWgB0jzm+CX4Srzz8RcXtLJDJB+BSccqMa36uyH/zUsSYao5+BD1ytv3k3rPYCq4mAE1hsXA== "@types/cacheable-request@^6.0.1": version "6.0.2" resolved "https://registry.yarnpkg.com/@types/cacheable-request/-/cacheable-request-6.0.2.tgz#c324da0197de0a98a2312156536ae262429ff6b9" integrity sha512-B3xVo+dlKM6nnKTcmm5ZtY/OL8bOAOd2Olee9M1zft65ox50OzjEHW91sDiU9j6cvW8Ejg1/Qkf4xd2kugApUA== dependencies: "@types/http-cache-semantics" "*" "@types/keyv" "*" "@types/node" "*" "@types/responselike" "*" "@types/chai@^3.4.35": version "3.5.2" resolved "https://registry.yarnpkg.com/@types/chai/-/chai-3.5.2.tgz#c11cd2817d3a401b7ba0f5a420f35c56139b1c1e" integrity sha1-wRzSgX06QBt7oPWkIPNcVhObHB4= "@types/codemirror@^0.0.76": version "0.0.76" resolved "https://registry.yarnpkg.com/@types/codemirror/-/codemirror-0.0.76.tgz#c52719878056a216bc05889b46a41d4eb1434423" integrity sha512-k/hpUb+Ebyn9z63qM8IbsRiW0eYHZ+pi/1e2reGzBKAZJzkjWmNTXXqLLiNv5d9ekyxkajxRBr5Hu2WZq/nokw== dependencies: "@types/tern" "*" "@types/component-emitter@^1.2.10": version "1.2.10" resolved "https://registry.yarnpkg.com/@types/component-emitter/-/component-emitter-1.2.10.tgz#ef5b1589b9f16544642e473db5ea5639107ef3ea" integrity sha512-bsjleuRKWmGqajMerkzox19aGbscQX5rmmvvXl3wlIp5gMG1HgkiwPxsN5p070fBDKTNSPgojVbuY1+HWMbFhg== "@types/cookie@^0.4.0": version "0.4.1" resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.4.1.tgz#bfd02c1f2224567676c1545199f87c3a861d878d" integrity sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q== "@types/cors@^2.8.8": version "2.8.12" resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.12.tgz#6b2c510a7ad7039e98e7b8d3d6598f4359e5c080" integrity sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw== "@types/debug@^4.1.4": version "4.1.7" resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.7.tgz#7cc0ea761509124709b8b2d1090d8f6c17aadb82" integrity sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg== dependencies: "@types/ms" "*" "@types/emscripten@^1.38.0": version "1.39.5" resolved "https://registry.yarnpkg.com/@types/emscripten/-/emscripten-1.39.5.tgz#eb0fb1048301df980b6f8a5ec3d63f7d1572bb73" integrity sha512-DIOOg+POSrYl+OlNRHQuIEqCd8DCtynG57H862UCce16nXJX7J8eWxNGgOcf8Eyge8zXeSs27mz1UcFu8L/L7g== "@types/estree@*": version "0.0.50" resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.50.tgz#1e0caa9364d3fccd2931c3ed96fdbeaa5d4cca83" integrity sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw== "@types/estree@0.0.39": version "0.0.39" resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f" integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw== "@types/events@*": version "3.0.0" resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7" integrity sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g== "@types/flat-cache@^2.0.0": version "2.0.0" resolved "https://registry.yarnpkg.com/@types/flat-cache/-/flat-cache-2.0.0.tgz#64e5d3b426c392b603a208a55bdcc7d920ce6e57" integrity sha512-fHeEsm9hvmZ+QHpw6Fkvf19KIhuqnYLU6vtWLjd5BsMd/qVi7iTkMioDZl0mQmfNRA1A6NwvhrSRNr9hGYZGww== "@types/glob@^7.1.1": version "7.1.4" resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.4.tgz#ea59e21d2ee5c517914cb4bc8e4153b99e566672" integrity sha512-w+LsMxKyYQm347Otw+IfBXOv9UWVjpHpCDdbBMt8Kz/xbvCYNjP+0qPh91Km3iKfSRLBB0P7fAMf0KHrPu+MyA== dependencies: "@types/minimatch" "*" "@types/node" "*" "@types/graphlib@^2": version "2.1.8" resolved "https://registry.yarnpkg.com/@types/graphlib/-/graphlib-2.1.8.tgz#9edd607e4b863a33b8b78cb08385c0be6896008a" integrity sha512-8nbbyD3zABRA9ePoBgAl2ym8cIwKQXTfv1gaIRTdY99yEOCaHfmjBeRp+BIemS8NtOqoWK7mfzWxjNrxLK3T5w== "@types/http-cache-semantics@*": version "4.0.1" resolved "https://registry.yarnpkg.com/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz#0ea7b61496902b95890dc4c3a116b60cb8dae812" integrity sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ== "@types/js-yaml@^3.12.1": version "3.12.7" resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-3.12.7.tgz#330c5d97a3500e9c903210d6e49f02964af04a0e" integrity sha512-S6+8JAYTE1qdsc9HMVsfY7+SgSuUU/Tp6TYTmITW0PZxiyIMvol3Gy//y69Wkhs0ti4py5qgR3uZH6uz/DNzJQ== "@types/json-schema@^7.0.3", "@types/json-schema@^7.0.5": version "7.0.9" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.9.tgz#97edc9037ea0c38585320b28964dde3b39e4660d" integrity sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ== "@types/keyv@*": version "3.1.2" resolved "https://registry.yarnpkg.com/@types/keyv/-/keyv-3.1.2.tgz#5d97bb65526c20b6e0845f6b0d2ade4f28604ee5" integrity sha512-/FvAK2p4jQOaJ6CGDHJTqZcUtbZe820qIeTg7o0Shg7drB4JHeL+V/dhSaly7NXx6u8eSee+r7coT+yuJEvDLg== dependencies: "@types/node" "*" "@types/lodash.chunk@^4.2.6": version "4.2.6" resolved "https://registry.yarnpkg.com/@types/lodash.chunk/-/lodash.chunk-4.2.6.tgz#9d35f05360b0298715d7f3d9efb34dd4f77e5d2a" integrity sha512-SPlusB7jxXyGcTXYcUdWr7WmhArO/rmTq54VN88iKMxGUhyg79I4Q8n4riGn3kjaTjOJrVlHhxgX/d7woak5BQ== dependencies: "@types/lodash" "*" "@types/lodash.omit@^4.5.6": version "4.5.6" resolved "https://registry.yarnpkg.com/@types/lodash.omit/-/lodash.omit-4.5.6.tgz#f2a9518259e481a48ff7ec423420fa8fd58933e2" integrity sha512-KXPpOSNX2h0DAG2w7ajpk7TXvWF28ZHs5nJhOJyP0BQHkehgr948RVsToItMme6oi0XJkp19CbuNXkIX8FiBlQ== dependencies: "@types/lodash" "*" "@types/lodash.pick@^4.4.6": version "4.4.6" resolved "https://registry.yarnpkg.com/@types/lodash.pick/-/lodash.pick-4.4.6.tgz#ae4e8f109e982786313bb6aac4b1a73aefa6e9be" integrity sha512-u8bzA16qQ+8dY280z3aK7PoWb3fzX5ATJ0rJB6F+uqchOX2VYF02Aqa+8aYiHiHgPzQiITqCgeimlyKFy4OA6g== dependencies: "@types/lodash" "*" "@types/lodash.union@^4.6.6": version "4.6.6" resolved "https://registry.yarnpkg.com/@types/lodash.union/-/lodash.union-4.6.6.tgz#2f77f2088326ed147819e9e384182b99aae8d4b0" integrity sha512-Wu0ZEVNcyCz8eAn6TlUbYWZoGbH9E+iOHxAZbwUoCEXdUiy6qpcz5o44mMXViM4vlPLLCPlkAubEP1gokoSZaw== dependencies: "@types/lodash" "*" "@types/lodash@*": version "4.14.172" resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.172.tgz#aad774c28e7bfd7a67de25408e03ee5a8c3d028a" integrity sha512-/BHF5HAx3em7/KkzVKm3LrsD6HZAXuXO1AJZQ3cRRBZj4oHZDviWPYu0aEplAqDFNHZPW6d3G7KN+ONcCCC7pw== "@types/mime-types@^2.1.0": version "2.1.0" resolved "https://registry.yarnpkg.com/@types/mime-types/-/mime-types-2.1.0.tgz#9ca52cda363f699c69466c2a6ccdaad913ea7a73" integrity sha1-nKUs2jY/aZxpRmwqbM2q2RPqenM= "@types/minimatch@*", "@types/minimatch@^3.0.3": version "3.0.5" resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.5.tgz#1001cc5e6a3704b83c236027e77f2f58ea010f40" integrity sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ== "@types/minimatch@3.0.3": version "3.0.3" resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA== "@types/minimist@^1.2.0": version "1.2.2" resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.2.tgz#ee771e2ba4b3dc5b372935d549fd9617bf345b8c" integrity sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ== "@types/mocha@^2.2.39": version "2.2.48" resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-2.2.48.tgz#3523b126a0b049482e1c3c11877460f76622ffab" integrity sha512-nlK/iyETgafGli8Zh9zJVCTicvU3iajSkRwOh3Hhiva598CMqNJ4NcVCGMTGKpGpTYj/9R8RLzS9NAykSSCqGw== "@types/ms@*": version "0.7.31" resolved "https://registry.yarnpkg.com/@types/ms/-/ms-0.7.31.tgz#31b7ca6407128a3d2bbc27fe2d21b345397f6197" integrity sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA== "@types/node@*", "@types/node@>=10.0.0": version "16.4.12" resolved "https://registry.yarnpkg.com/@types/node/-/node-16.4.12.tgz#961e3091f263e6345d2d84afab4e047a60b4b11b" integrity sha512-zxrTNFl9Z8boMJXs6ieqZP0wAhvkdzmHSxTlJabM16cf5G9xBc1uPRH5Bbv2omEDDiM8MzTfqTJXBf0Ba4xFWA== "@types/node@10.17.13": version "10.17.13" resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.13.tgz#ccebcdb990bd6139cd16e84c39dc2fb1023ca90c" integrity sha512-pMCcqU2zT4TjqYFrWtYHKal7Sl30Ims6ulZ4UFXxI4xbtQqK/qqKwkDoBFCfooRqqmRu9vY3xaJRwxSh673aYg== "@types/node@^10.12.19": version "10.17.60" resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.60.tgz#35f3d6213daed95da7f0f73e75bcc6980e90597b" integrity sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw== "@types/node@^12.12.17": version "12.20.19" resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.19.tgz#538e61fc220f77ae4a4663c3d8c3cb391365c209" integrity sha512-niAuZrwrjKck4+XhoCw6AAVQBENHftpXw9F4ryk66fTgYaKQ53R4FI7c9vUGGw5vQis1HKBHDR1gcYI/Bq1xvw== "@types/node@^13.7.0": version "13.13.52" resolved "https://registry.yarnpkg.com/@types/node/-/node-13.13.52.tgz#03c13be70b9031baaed79481c0c0cfb0045e53f7" integrity sha512-s3nugnZumCC//n4moGGe6tkNMyYEdaDBitVjwPxXmR5lnMG5dHePinH2EdxkG3Rh1ghFHHixAG4NJhpJW1rthQ== "@types/normalize-package-data@^2.4.0": version "2.4.1" resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz#d3357479a0fdfdd5907fe67e17e0a85c906e1301" integrity sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw== "@types/parse-json@^4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== "@types/q@^1.5.1": version "1.5.5" resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.5.tgz#75a2a8e7d8ab4b230414505d92335d1dcb53a6df" integrity sha512-L28j2FcJfSZOnL1WBjDYp2vUHCeIFlyYI/53EwD/rKUBQ7MtUUfbQWiyKJGpcnv4/WgrhWsFKrcPstcAt/J0tQ== "@types/resolve@0.0.8": version "0.0.8" resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-0.0.8.tgz#f26074d238e02659e323ce1a13d041eee280e194" integrity sha512-auApPaJf3NPfe18hSoJkp8EbZzer2ISk7o8mCC3M9he/a04+gbMF97NkpD2S8riMGvm4BMRI59/SZQSaLTKpsQ== dependencies: "@types/node" "*" "@types/responselike@*", "@types/responselike@^1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@types/responselike/-/responselike-1.0.0.tgz#251f4fe7d154d2bad125abe1b429b23afd262e29" integrity sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA== dependencies: "@types/node" "*" "@types/sarif@^2.1.3": version "2.1.4" resolved "https://registry.yarnpkg.com/@types/sarif/-/sarif-2.1.4.tgz#471c5788199d22f572f255de9a8166a30abf1245" integrity sha512-4xKHMdg3foh3Va1fxTzY1qt8QVqmaJpGWsVvtjQrJBn+/bkig2pWFKJ4FPI2yLI4PAj0SUKiPO4Vd7ggYIMZjQ== "@types/semver@^7.1.0": version "7.3.8" resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.8.tgz#508a27995498d7586dcecd77c25e289bfaf90c59" integrity sha512-D/2EJvAlCEtYFEYmmlGwbGXuK886HzyCc3nZX/tkFTQdEU8jZDAgiv08P162yB17y4ZXZoq7yFAnW4GDBb9Now== "@types/tern@*": version "0.23.4" resolved "https://registry.yarnpkg.com/@types/tern/-/tern-0.23.4.tgz#03926eb13dbeaf3ae0d390caf706b2643a0127fb" integrity sha512-JAUw1iXGO1qaWwEOzxTKJZ/5JxVeON9kvGZ/osgZaJImBnyjyn0cjovPsf6FNLmyGY8Vw9DoXZCMlfMkMwHRWg== dependencies: "@types/estree" "*" "@types/treeify@^1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@types/treeify/-/treeify-1.0.0.tgz#f04743cb91fc38254e8585d692bd92503782011c" integrity sha512-ONpcZAEYlbPx4EtJwfTyCDQJGUpKf4sEcuySdCVjK5Fj/3vHp5HII1fqa1/+qrsLnpYELCQTfVW/awsGJePoIg== "@types/uuid@^8.3.0": version "8.3.1" resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.1.tgz#1a32969cf8f0364b3d8c8af9cc3555b7805df14f" integrity sha512-Y2mHTRAbqfFkpjldbkHGY8JIzRN6XqYRliG8/24FcHm2D2PwW24fl5xMRTVGdrb7iMrwCaIEbLWerGIkXuFWVg== "@types/websocket@^0.0.40": version "0.0.40" resolved "https://registry.yarnpkg.com/@types/websocket/-/websocket-0.0.40.tgz#887cd632a8b3d0d11da7b9d0d106af85997b358c" integrity sha512-ldteZwWIgl9cOy7FyvYn+39Ah4+PfpVE72eYKw75iy2L0zTbhbcwvzeJ5IOu6DQP93bjfXq0NGHY6FYtmYoqFQ== dependencies: "@types/events" "*" "@types/node" "*" "@typescript-eslint/eslint-plugin@~4.8.1": version "4.8.2" resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.8.2.tgz#cf9102ec800391caa574f589ffe0623cca1d9308" integrity sha512-gQ06QLV5l1DtvYtqOyFLXD9PdcILYqlrJj2l+CGDlPtmgLUzc1GpqciJFIRvyfvgLALpnxYINFuw+n9AZhPBKQ== dependencies: "@typescript-eslint/experimental-utils" "4.8.2" "@typescript-eslint/scope-manager" "4.8.2" debug "^4.1.1" functional-red-black-tree "^1.0.1" regexpp "^3.0.0" semver "^7.3.2" tsutils "^3.17.1" "@typescript-eslint/experimental-utils@4.8.2": version "4.8.2" resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.8.2.tgz#8909a5732f19329cf5ef0c39766170476bff5e50" integrity sha512-hpTw6o6IhBZEsQsjuw/4RWmceRyESfAiEzAEnXHKG1X7S5DXFaZ4IO1JO7CW1aQ604leQBzjZmuMI9QBCAJX8Q== dependencies: "@types/json-schema" "^7.0.3" "@typescript-eslint/scope-manager" "4.8.2" "@typescript-eslint/types" "4.8.2" "@typescript-eslint/typescript-estree" "4.8.2" eslint-scope "^5.0.0" eslint-utils "^2.0.0" "@typescript-eslint/parser@~4.8.1": version "4.8.2" resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.8.2.tgz#78dccbe5124de2b8dea2d4c363dee9f769151ca8" integrity sha512-u0leyJqmclYr3KcXOqd2fmx6SDGBO0MUNHHAjr0JS4Crbb3C3d8dwAdlazy133PLCcPn+aOUFiHn72wcuc5wYw== dependencies: "@typescript-eslint/scope-manager" "4.8.2" "@typescript-eslint/types" "4.8.2" "@typescript-eslint/typescript-estree" "4.8.2" debug "^4.1.1" "@typescript-eslint/scope-manager@4.8.2": version "4.8.2" resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.8.2.tgz#a18388c63ae9c17adde519384f539392f2c4f0d9" integrity sha512-qHQ8ODi7mMin4Sq2eh/6eu03uVzsf5TX+J43xRmiq8ujng7ViQSHNPLOHGw/Wr5dFEoxq/ubKhzClIIdQy5q3g== dependencies: "@typescript-eslint/types" "4.8.2" "@typescript-eslint/visitor-keys" "4.8.2" "@typescript-eslint/types@4.8.2": version "4.8.2" resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.8.2.tgz#c862dd0e569d9478eb82d6aee662ea53f5661a36" integrity sha512-z1/AVcVF8ju5ObaHe2fOpZYEQrwHyZ7PTOlmjd3EoFeX9sv7UekQhfrCmgUO7PruLNfSHrJGQvrW3Q7xQ8EoAw== "@typescript-eslint/typescript-estree@4.8.2": version "4.8.2" resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.8.2.tgz#eeec34707d8577600fb21661b5287226cc8b3bed" integrity sha512-HToGNwI6fekH0dOw3XEVESUm71Onfam0AKin6f26S2FtUmO7o3cLlWgrIaT1q3vjB3wCTdww3Dx2iGq5wtUOCg== dependencies: "@typescript-eslint/types" "4.8.2" "@typescript-eslint/visitor-keys" "4.8.2" debug "^4.1.1" globby "^11.0.1" is-glob "^4.0.1" lodash "^4.17.15" semver "^7.3.2" tsutils "^3.17.1" "@typescript-eslint/visitor-keys@4.8.2": version "4.8.2" resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.8.2.tgz#62cd3fbbbf65f8eccfbe6f159eb1b84a243a3f77" integrity sha512-Vg+/SJTMZJEKKGHW7YC21QxgKJrSbxoYYd3MEUGtW7zuytHuEcksewq0DUmo4eh/CTNrVJGSdIY9AtRb6riWFw== dependencies: "@typescript-eslint/types" "4.8.2" eslint-visitor-keys "^2.0.0" "@ungap/promise-all-settled@1.1.2": version "1.1.2" resolved "https://registry.yarnpkg.com/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz#aa58042711d6e3275dd37dc597e5d31e8c290a44" integrity sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q== "@verdaccio/commons-api@10.0.0": version "10.0.0" resolved "https://registry.yarnpkg.com/@verdaccio/commons-api/-/commons-api-10.0.0.tgz#2d7de8722f94181f1a71891fe91198a7c14e6dea" integrity sha512-UC8wrRI9FvqjfDeB1RijF7aVI0JJhCOI8RkEDibCT/JD8zVngphrNmgSWcjo8Es3lRiu7NugWXDSuggCCeCfUg== dependencies: http-errors "1.8.0" http-status-codes "1.4.0" "@verdaccio/file-locking@10.0.0", "@verdaccio/file-locking@^10.0.0": version "10.0.0" resolved "https://registry.yarnpkg.com/@verdaccio/file-locking/-/file-locking-10.0.0.tgz#3d476a6ba28207c795d49828438e7335166c1cfc" integrity sha512-2tQUbJF3tQ3CY9grAlpovaF/zu8G56CBYMaeHwMBHo9rAmsJI9i7LfliHGS6Jygbs8vd0cOCPT7vl2CL9T8upw== dependencies: lockfile "1.0.4" "@verdaccio/local-storage@10.0.6": version "10.0.6" resolved "https://registry.yarnpkg.com/@verdaccio/local-storage/-/local-storage-10.0.6.tgz#be485a8107ad84206cf80702d325ca47b7f22f68" integrity sha512-YEImOMUL56lziS/N3o1YzoOcVGZXpyZclGSonw7XQ1lKQEvEhU06V2+tIdjPgtqIOuH9ZKdPeBsBuN7ILa2qzQ== dependencies: "@verdaccio/commons-api" "10.0.0" "@verdaccio/file-locking" "10.0.0" "@verdaccio/streams" "10.0.0" async "3.2.0" debug "4.3.1" lodash "4.17.21" lowdb "1.0.0" mkdirp "1.0.4" "@verdaccio/readme@10.0.0": version "10.0.0" resolved "https://registry.yarnpkg.com/@verdaccio/readme/-/readme-10.0.0.tgz#f9627c32b309ace311318b98b2c42226823f6cd7" integrity sha512-OD3dMnRC8SvhgytEzczMBleN+K/3lMqyWw/epeXvolCpCd7mW/Dl5zSR25GiHh/2h3eTKP/HMs4km8gS1MMLgA== dependencies: dompurify "^2.2.6" jsdom "15.2.1" marked "^2.0.1" "@verdaccio/streams@10.0.0": version "10.0.0" resolved "https://registry.yarnpkg.com/@verdaccio/streams/-/streams-10.0.0.tgz#8b06e1d6f06e906ebda0f1d4089cdb651a533541" integrity sha512-PqxxY11HhweN6z1lwfn9ydLCdnOkCPpthMZs+SGCDz8Rt6gOyrjJVslV7o4uobDipjD9+hUPpJHDeO33Qt24uw== "@verdaccio/ui-theme@3.1.0": version "3.1.0" resolved "https://registry.yarnpkg.com/@verdaccio/ui-theme/-/ui-theme-3.1.0.tgz#21108f3c1b97e6db5901509d935e1f4ce475950a" integrity sha512-NmJOcv25/OtF84YrmYxi31beFde7rt+/y2qlnq0wYR4ZCFRE5TsuqisTVTe1OyJ8D8JwwPMyMSMSMtlMwUfqIQ== "@webassemblyjs/ast@1.9.0": version "1.9.0" resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.9.0.tgz#bd850604b4042459a5a41cd7d338cbed695ed964" integrity sha512-C6wW5L+b7ogSDVqymbkkvuW9kruN//YisMED04xzeBBqjHa2FYnmvOlS6Xj68xWQRgWvI9cIglsjFowH/RJyEA== dependencies: "@webassemblyjs/helper-module-context" "1.9.0" "@webassemblyjs/helper-wasm-bytecode" "1.9.0" "@webassemblyjs/wast-parser" "1.9.0" "@webassemblyjs/floating-point-hex-parser@1.9.0": version "1.9.0" resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.9.0.tgz#3c3d3b271bddfc84deb00f71344438311d52ffb4" integrity sha512-TG5qcFsS8QB4g4MhrxK5TqfdNe7Ey/7YL/xN+36rRjl/BlGE/NcBvJcqsRgCP6Z92mRE+7N50pRIi8SmKUbcQA== "@webassemblyjs/helper-api-error@1.9.0": version "1.9.0" resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.9.0.tgz#203f676e333b96c9da2eeab3ccef33c45928b6a2" integrity sha512-NcMLjoFMXpsASZFxJ5h2HZRcEhDkvnNFOAKneP5RbKRzaWJN36NC4jqQHKwStIhGXu5mUWlUUk7ygdtrO8lbmw== "@webassemblyjs/helper-buffer@1.9.0": version "1.9.0" resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.9.0.tgz#a1442d269c5feb23fcbc9ef759dac3547f29de00" integrity sha512-qZol43oqhq6yBPx7YM3m9Bv7WMV9Eevj6kMi6InKOuZxhw+q9hOkvq5e/PpKSiLfyetpaBnogSbNCfBwyB00CA== "@webassemblyjs/helper-code-frame@1.9.0": version "1.9.0" resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.9.0.tgz#647f8892cd2043a82ac0c8c5e75c36f1d9159f27" integrity sha512-ERCYdJBkD9Vu4vtjUYe8LZruWuNIToYq/ME22igL+2vj2dQ2OOujIZr3MEFvfEaqKoVqpsFKAGsRdBSBjrIvZA== dependencies: "@webassemblyjs/wast-printer" "1.9.0" "@webassemblyjs/helper-fsm@1.9.0": version "1.9.0" resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-fsm/-/helper-fsm-1.9.0.tgz#c05256b71244214671f4b08ec108ad63b70eddb8" integrity sha512-OPRowhGbshCb5PxJ8LocpdX9Kl0uB4XsAjl6jH/dWKlk/mzsANvhwbiULsaiqT5GZGT9qinTICdj6PLuM5gslw== "@webassemblyjs/helper-module-context@1.9.0": version "1.9.0" resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-module-context/-/helper-module-context-1.9.0.tgz#25d8884b76839871a08a6c6f806c3979ef712f07" integrity sha512-MJCW8iGC08tMk2enck1aPW+BE5Cw8/7ph/VGZxwyvGbJwjktKkDK7vy7gAmMDx88D7mhDTCNKAW5tED+gZ0W8g== dependencies: "@webassemblyjs/ast" "1.9.0" "@webassemblyjs/helper-wasm-bytecode@1.9.0": version "1.9.0" resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.9.0.tgz#4fed8beac9b8c14f8c58b70d124d549dd1fe5790" integrity sha512-R7FStIzyNcd7xKxCZH5lE0Bqy+hGTwS3LJjuv1ZVxd9O7eHCedSdrId/hMOd20I+v8wDXEn+bjfKDLzTepoaUw== "@webassemblyjs/helper-wasm-section@1.9.0": version "1.9.0" resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.9.0.tgz#5a4138d5a6292ba18b04c5ae49717e4167965346" integrity sha512-XnMB8l3ek4tvrKUUku+IVaXNHz2YsJyOOmz+MMkZvh8h1uSJpSen6vYnw3IoQ7WwEuAhL8Efjms1ZWjqh2agvw== dependencies: "@webassemblyjs/ast" "1.9.0" "@webassemblyjs/helper-buffer" "1.9.0" "@webassemblyjs/helper-wasm-bytecode" "1.9.0" "@webassemblyjs/wasm-gen" "1.9.0" "@webassemblyjs/ieee754@1.9.0": version "1.9.0" resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.9.0.tgz#15c7a0fbaae83fb26143bbacf6d6df1702ad39e4" integrity sha512-dcX8JuYU/gvymzIHc9DgxTzUUTLexWwt8uCTWP3otys596io0L5aW02Gb1RjYpx2+0Jus1h4ZFqjla7umFniTg== dependencies: "@xtuc/ieee754" "^1.2.0" "@webassemblyjs/leb128@1.9.0": version "1.9.0" resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.9.0.tgz#f19ca0b76a6dc55623a09cffa769e838fa1e1c95" integrity sha512-ENVzM5VwV1ojs9jam6vPys97B/S65YQtv/aanqnU7D8aSoHFX8GyhGg0CMfyKNIHBuAVjy3tlzd5QMMINa7wpw== dependencies: "@xtuc/long" "4.2.2" "@webassemblyjs/utf8@1.9.0": version "1.9.0" resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.9.0.tgz#04d33b636f78e6a6813227e82402f7637b6229ab" integrity sha512-GZbQlWtopBTP0u7cHrEx+73yZKrQoBMpwkGEIqlacljhXCkVM1kMQge/Mf+csMJAjEdSwhOyLAS0AoR3AG5P8w== "@webassemblyjs/wasm-edit@1.9.0": version "1.9.0" resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.9.0.tgz#3fe6d79d3f0f922183aa86002c42dd256cfee9cf" integrity sha512-FgHzBm80uwz5M8WKnMTn6j/sVbqilPdQXTWraSjBwFXSYGirpkSWE2R9Qvz9tNiTKQvoKILpCuTjBKzOIm0nxw== dependencies: "@webassemblyjs/ast" "1.9.0" "@webassemblyjs/helper-buffer" "1.9.0" "@webassemblyjs/helper-wasm-bytecode" "1.9.0" "@webassemblyjs/helper-wasm-section" "1.9.0" "@webassemblyjs/wasm-gen" "1.9.0" "@webassemblyjs/wasm-opt" "1.9.0" "@webassemblyjs/wasm-parser" "1.9.0" "@webassemblyjs/wast-printer" "1.9.0" "@webassemblyjs/wasm-gen@1.9.0": version "1.9.0" resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.9.0.tgz#50bc70ec68ded8e2763b01a1418bf43491a7a49c" integrity sha512-cPE3o44YzOOHvlsb4+E9qSqjc9Qf9Na1OO/BHFy4OI91XDE14MjFN4lTMezzaIWdPqHnsTodGGNP+iRSYfGkjA== dependencies: "@webassemblyjs/ast" "1.9.0" "@webassemblyjs/helper-wasm-bytecode" "1.9.0" "@webassemblyjs/ieee754" "1.9.0" "@webassemblyjs/leb128" "1.9.0" "@webassemblyjs/utf8" "1.9.0" "@webassemblyjs/wasm-opt@1.9.0": version "1.9.0" resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.9.0.tgz#2211181e5b31326443cc8112eb9f0b9028721a61" integrity sha512-Qkjgm6Anhm+OMbIL0iokO7meajkzQD71ioelnfPEj6r4eOFuqm4YC3VBPqXjFyyNwowzbMD+hizmprP/Fwkl2A== dependencies: "@webassemblyjs/ast" "1.9.0" "@webassemblyjs/helper-buffer" "1.9.0" "@webassemblyjs/wasm-gen" "1.9.0" "@webassemblyjs/wasm-parser" "1.9.0" "@webassemblyjs/wasm-parser@1.9.0": version "1.9.0" resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.9.0.tgz#9d48e44826df4a6598294aa6c87469d642fff65e" integrity sha512-9+wkMowR2AmdSWQzsPEjFU7njh8HTO5MqO8vjwEHuM+AMHioNqSBONRdr0NQQ3dVQrzp0s8lTcYqzUdb7YgELA== dependencies: "@webassemblyjs/ast" "1.9.0" "@webassemblyjs/helper-api-error" "1.9.0" "@webassemblyjs/helper-wasm-bytecode" "1.9.0" "@webassemblyjs/ieee754" "1.9.0" "@webassemblyjs/leb128" "1.9.0" "@webassemblyjs/utf8" "1.9.0" "@webassemblyjs/wast-parser@1.9.0": version "1.9.0" resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-parser/-/wast-parser-1.9.0.tgz#3031115d79ac5bd261556cecc3fa90a3ef451914" integrity sha512-qsqSAP3QQ3LyZjNC/0jBJ/ToSxfYJ8kYyuiGvtn/8MK89VrNEfwj7BPQzJVHi0jGTRK2dGdJ5PRqhtjzoww+bw== dependencies: "@webassemblyjs/ast" "1.9.0" "@webassemblyjs/floating-point-hex-parser" "1.9.0" "@webassemblyjs/helper-api-error" "1.9.0" "@webassemblyjs/helper-code-frame" "1.9.0" "@webassemblyjs/helper-fsm" "1.9.0" "@xtuc/long" "4.2.2" "@webassemblyjs/wast-printer@1.9.0": version "1.9.0" resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.9.0.tgz#4935d54c85fef637b00ce9f52377451d00d47899" integrity sha512-2J0nE95rHXHyQ24cWjMKJ1tqB/ds8z/cyeOZxJhcb+rW+SQASVjuznUSmdz5GpVJTzU8JkhYut0D3siFDD6wsA== dependencies: "@webassemblyjs/ast" "1.9.0" "@webassemblyjs/wast-parser" "1.9.0" "@xtuc/long" "4.2.2" "@xtuc/ieee754@^1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" integrity sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA== "@xtuc/long@4.2.2": version "4.2.2" resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== "@yarnpkg/core@^2.4.0": version "2.4.0" resolved "https://registry.yarnpkg.com/@yarnpkg/core/-/core-2.4.0.tgz#b5d8cc7ee2ddb022816c7afa3f83c3ee3d317c80" integrity sha512-FYjcPNTfDfMKLFafQPt49EY28jnYC82Z2S7oMwLPUh144BL8v8YXzb4aCnFyi5nFC5h2kcrJfZh7+Pm/qvCqGw== dependencies: "@arcanis/slice-ansi" "^1.0.2" "@types/semver" "^7.1.0" "@types/treeify" "^1.0.0" "@yarnpkg/fslib" "^2.4.0" "@yarnpkg/json-proxy" "^2.1.0" "@yarnpkg/libzip" "^2.2.1" "@yarnpkg/parsers" "^2.3.0" "@yarnpkg/pnp" "^2.3.2" "@yarnpkg/shell" "^2.4.1" binjumper "^0.1.4" camelcase "^5.3.1" chalk "^3.0.0" ci-info "^2.0.0" clipanion "^2.6.2" cross-spawn "7.0.3" diff "^4.0.1" globby "^11.0.1" got "^11.7.0" json-file-plus "^3.3.1" lodash "^4.17.15" micromatch "^4.0.2" mkdirp "^0.5.1" p-limit "^2.2.0" pluralize "^7.0.0" pretty-bytes "^5.1.0" semver "^7.1.2" stream-to-promise "^2.2.0" tar-stream "^2.0.1" treeify "^1.1.0" tslib "^1.13.0" tunnel "^0.0.6" "@yarnpkg/fslib@^2.4.0", "@yarnpkg/fslib@^2.5.0": version "2.5.0" resolved "https://registry.yarnpkg.com/@yarnpkg/fslib/-/fslib-2.5.0.tgz#d0f4b0d70d89ab2de5ad3349e59cfde87a7e6c1c" integrity sha512-xkKmuW3HwQeWOPqOhBCbDjTGbgimP/VWN2bPpx4FnfgbVj1xjULyOtZR5h9p49jA7IIZsccG91+Ad9kLZ2A4DA== dependencies: "@yarnpkg/libzip" "^2.2.2" tslib "^1.13.0" "@yarnpkg/json-proxy@^2.1.0": version "2.1.1" resolved "https://registry.yarnpkg.com/@yarnpkg/json-proxy/-/json-proxy-2.1.1.tgz#47866706061e9bdb15b0d199f6f08dde655bf569" integrity sha512-meUiCAgCYpXTH1qJfqfz+dX013ohW9p2dKfwIzUYAFutH+lsz1eHPBIk72cuCV84adh9gX6j66ekBKH/bIhCQw== dependencies: "@yarnpkg/fslib" "^2.5.0" tslib "^1.13.0" "@yarnpkg/libzip@^2.2.1", "@yarnpkg/libzip@^2.2.2": version "2.2.2" resolved "https://registry.yarnpkg.com/@yarnpkg/libzip/-/libzip-2.2.2.tgz#365416c1a973cb034b82e4bcb6e91e90879811c7" integrity sha512-M7ziz16f+tFFnJSCreLtemaGPpjT+NC0E21JQaWXAAlRmFIXz6zl2EZ+tXLxV9yJaplpNDbTgX1j5GPiwg5H5w== dependencies: "@types/emscripten" "^1.38.0" tslib "^1.13.0" "@yarnpkg/lockfile@^1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31" integrity sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ== "@yarnpkg/parsers@^2.3.0": version "2.4.0" resolved "https://registry.yarnpkg.com/@yarnpkg/parsers/-/parsers-2.4.0.tgz#5d092939022c96b256eaea461816ef2b7d77fa1b" integrity sha512-XWgiNGh4MkhdBTJVEbXEqzk66JKjvxTtKGeLPqo3rnJ7JiJnRaK2n9MLTKQB0uoRMWYzPlISdIlok6H9OdlOVQ== dependencies: js-yaml "^3.10.0" tslib "^1.13.0" "@yarnpkg/pnp@^2.3.2": version "2.3.2" resolved "https://registry.yarnpkg.com/@yarnpkg/pnp/-/pnp-2.3.2.tgz#9a052a06bf09c9f0b7c31e0867a7e725cb6401ed" integrity sha512-JdwHu1WBCISqJEhIwx6Hbpe8MYsYbkGMxoxolkDiAeJ9IGEe08mQcbX1YmUDV1ozSWlm9JZE90nMylcDsXRFpA== dependencies: "@types/node" "^13.7.0" "@yarnpkg/fslib" "^2.4.0" tslib "^1.13.0" "@yarnpkg/shell@^2.4.1": version "2.4.1" resolved "https://registry.yarnpkg.com/@yarnpkg/shell/-/shell-2.4.1.tgz#abc557f8924987c9c382703e897433a82780265d" integrity sha512-oNNJkH8ZI5uwu0dMkJf737yMSY1WXn9gp55DqSA5wAOhKvV5DJTXFETxkVgBQhO6Bow9tMGSpvowTMD/oAW/9g== dependencies: "@yarnpkg/fslib" "^2.4.0" "@yarnpkg/parsers" "^2.3.0" clipanion "^2.6.2" cross-spawn "7.0.3" fast-glob "^3.2.2" micromatch "^4.0.2" stream-buffers "^3.0.2" tslib "^1.13.0" JSONStream@1.3.5, JSONStream@^1.0.4: version "1.3.5" resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.5.tgz#3208c1f08d3a4d99261ab64f92302bc15e111ca0" integrity sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ== dependencies: jsonparse "^1.2.0" through ">=2.2.7 <3" abab@^2.0.0: version "2.0.5" resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.5.tgz#c0b678fb32d60fc1219c784d6a826fe385aeb79a" integrity sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q== abbrev@1, abbrev@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.7: version "1.3.7" resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd" integrity sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA== dependencies: mime-types "~2.1.24" negotiator "0.6.2" acorn-globals@^4.3.2: version "4.3.4" resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-4.3.4.tgz#9fa1926addc11c97308c4e66d7add0d40c3272e7" integrity sha512-clfQEh21R+D0leSbUdWf3OcfqyaCSAQ8Ryq00bofSekfr9W8u1jyYZo6ir0xu9Gtcf7BjcHJpnbZH7JOCpP60A== dependencies: acorn "^6.0.1" acorn-walk "^6.0.1" acorn-jsx@^5.3.1: version "5.3.2" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== acorn-walk@^6.0.1: version "6.2.0" resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-6.2.0.tgz#123cb8f3b84c2171f1f7fb252615b1c78a6b1a8c" integrity sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA== acorn@^6.0.1, acorn@^6.4.1: version "6.4.2" resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.2.tgz#35866fd710528e92de10cf06016498e47e39e1e6" integrity sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ== acorn@^7.1.0, acorn@^7.4.0: version "7.4.1" resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== add-stream@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/add-stream/-/add-stream-1.0.0.tgz#6a7990437ca736d5e1288db92bd3266d5f5cb2aa" integrity sha1-anmQQ3ynNtXhKI25K9MmbV9csqo= agent-base@5: version "5.1.1" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-5.1.1.tgz#e8fb3f242959db44d63be665db7a8e739537a32c" integrity sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g== agent-base@6, agent-base@^6.0.2: version "6.0.2" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== dependencies: debug "4" agentkeepalive@^4.1.3: version "4.1.4" resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.1.4.tgz#d928028a4862cb11718e55227872e842a44c945b" integrity sha512-+V/rGa3EuU74H6wR04plBb7Ks10FbtUQgRj/FQOG7uUIEuaINI+AiqJR1k6t3SVNs7o7ZjIdus6706qqzVq8jQ== dependencies: debug "^4.1.0" depd "^1.1.2" humanize-ms "^1.2.1" aggregate-error@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA== dependencies: clean-stack "^2.0.0" indent-string "^4.0.0" ajv-errors@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/ajv-errors/-/ajv-errors-1.0.1.tgz#f35986aceb91afadec4102fbd85014950cefa64d" integrity sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ== ajv-keywords@^3.1.0, ajv-keywords@^3.4.1, ajv-keywords@^3.5.2: version "3.5.2" resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.3, ajv@^6.12.4, ajv@~6.12.6: version "6.12.6" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== dependencies: fast-deep-equal "^3.1.1" fast-json-stable-stringify "^2.0.0" json-schema-traverse "^0.4.1" uri-js "^4.2.2" alphanum-sort@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3" integrity sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM= ansi-align@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-3.0.0.tgz#b536b371cf687caaef236c18d3e21fe3797467cb" integrity sha512-ZpClVKqXN3RGBmKibdfWzqCY4lnjEuoNzU5T0oEFpfd/z5qJHVarukridD4juLO2FXMiwUQxr9WqQtaYa8XRYw== dependencies: string-width "^3.0.0" ansi-colors@4.1.1, ansi-colors@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== ansi-escapes@3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b" integrity sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ== ansi-escapes@^4.2.1, ansi-escapes@^4.3.0: version "4.3.2" resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== dependencies: type-fest "^0.21.3" ansi-regex@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= ansi-regex@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= ansi-regex@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== ansi-regex@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75" integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg== ansi-styles@^3.2.0, ansi-styles@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== dependencies: color-convert "^1.9.0" ansi-styles@^4.0.0, ansi-styles@^4.1.0: version "4.3.0" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== dependencies: color-convert "^2.0.1" ansicolors@^0.3.2: version "0.3.2" resolved "https://registry.yarnpkg.com/ansicolors/-/ansicolors-0.3.2.tgz#665597de86a9ffe3aa9bfbe6cae5c6ea426b4979" integrity sha1-ZlWX3oap/+Oqm/vmyuXG6kJrSXk= any-promise@^1.1.0, any-promise@~1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" integrity sha1-q8av7tzqUugJzcA3au0845Y10X8= anymatch@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb" integrity sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw== dependencies: micromatch "^3.1.4" normalize-path "^2.1.1" anymatch@~3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== dependencies: normalize-path "^3.0.0" picomatch "^2.0.4" apache-md5@1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/apache-md5/-/apache-md5-1.1.2.tgz#ee49736b639b4f108b6e9e626c6da99306b41692" integrity sha1-7klza2ObTxCLbp5ibG2pkwa0FpI= aproba@^1.0.3, aproba@^1.1.1: version "1.2.0" resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== aproba@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/aproba/-/aproba-2.0.0.tgz#52520b8ae5b569215b354efc0caa3fe1e45a8adc" integrity sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ== archy@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/archy/-/archy-1.0.0.tgz#f9c8c13757cc1dd7bc379ac77b2c62a5c2868c40" integrity sha1-+cjBN1fMHde8N5rHeyxipcKGjEA= are-we-there-yet@~1.1.2: version "1.1.5" resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz#4b35c2944f062a8bfcda66410760350fe9ddfc21" integrity sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w== dependencies: delegates "^1.0.0" readable-stream "^2.0.6" argparse@^1.0.7, argparse@~1.0.9: version "1.0.10" resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== dependencies: sprintf-js "~1.0.2" argparse@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== arr-diff@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" integrity sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA= arr-flatten@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" integrity sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg== arr-union@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ= array-differ@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/array-differ/-/array-differ-3.0.0.tgz#3cbb3d0f316810eafcc47624734237d6aee4ae6b" integrity sha512-THtfYS6KtME/yIAhKjZ2ul7XI96lQGHRputJQHO80LAWQnuGP4iCIN8vdMRboGbIEYBwU33q8Tch1os2+X0kMg== array-equal@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/array-equal/-/array-equal-1.0.0.tgz#8c2a5ef2472fd9ea742b04c77a75093ba2757c93" integrity sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM= array-flatten@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI= array-ify@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/array-ify/-/array-ify-1.0.0.tgz#9e528762b4a9066ad163a6962a364418e9626ece" integrity sha1-nlKHYrSpBmrRY6aWKjZEGOlibs4= array-union@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== array-unique@^0.3.2: version "0.3.2" resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg= arrify@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" integrity sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0= arrify@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/arrify/-/arrify-2.0.1.tgz#c9655e9331e0abcd588d2a7cad7e9956f66701fa" integrity sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug== asap@^2.0.0, asap@~2.0.3: version "2.0.6" resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY= asn1.js@^5.2.0: version "5.4.1" resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-5.4.1.tgz#11a980b84ebb91781ce35b0fdc2ee294e3783f07" integrity sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA== dependencies: bn.js "^4.0.0" inherits "^2.0.1" minimalistic-assert "^1.0.0" safer-buffer "^2.1.0" asn1@~0.2.0, asn1@~0.2.3: version "0.2.4" resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg== dependencies: safer-buffer "~2.1.0" assert-plus@1.0.0, assert-plus@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= assert@^1.1.1: version "1.5.0" resolved "https://registry.yarnpkg.com/assert/-/assert-1.5.0.tgz#55c109aaf6e0aefdb3dc4b71240c70bf574b18eb" integrity sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA== dependencies: object-assign "^4.1.1" util "0.10.3" assertion-error@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b" integrity sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw== assign-symbols@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c= astral-regex@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9" integrity sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg== astral-regex@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31" integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== async-each@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf" integrity sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ== async-limiter@~1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd" integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ== async@3.2.0, async@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/async/-/async-3.2.0.tgz#b3a2685c5ebb641d3de02d161002c60fc9f85720" integrity sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw== async@^2.5.0: version "2.6.3" resolved "https://registry.yarnpkg.com/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff" integrity sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg== dependencies: lodash "^4.17.14" asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= at-least-node@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2" integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg== atob@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== atomic-sleep@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/atomic-sleep/-/atomic-sleep-1.0.0.tgz#eb85b77a601fc932cfe432c5acd364a9e2c9075b" integrity sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ== aws-sign2@~0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg= aws4@^1.8.0: version "1.11.0" resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59" integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA== backbone@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/backbone/-/backbone-1.4.0.tgz#54db4de9df7c3811c3f032f34749a4cd27f3bd12" integrity sha512-RLmDrRXkVdouTg38jcgHhyQ/2zjg7a8E6sz2zxfz21Hh17xDJYUHBZimVIt5fUyS8vbfpeSmTL3gUjTEvUV3qQ== dependencies: underscore ">=1.8.3" balanced-match@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== base64-arraybuffer@0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.1.4.tgz#9818c79e059b1355f97e0428a017c838e90ba812" integrity sha1-mBjHngWbE1X5fgQooBfIOOkLqBI= base64-js@^1.0.2, base64-js@^1.3.1: version "1.5.1" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== base64id@2.0.0, base64id@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/base64id/-/base64id-2.0.0.tgz#2770ac6bc47d312af97a8bf9a634342e0cd25cb6" integrity sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog== base@^0.11.1: version "0.11.2" resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f" integrity sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg== dependencies: cache-base "^1.0.1" class-utils "^0.3.5" component-emitter "^1.2.1" define-property "^1.0.0" isobject "^3.0.1" mixin-deep "^1.2.0" pascalcase "^0.1.1" bcrypt-pbkdf@^1.0.0, bcrypt-pbkdf@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4= dependencies: tweetnacl "^0.14.3" bcryptjs@2.4.3: version "2.4.3" resolved "https://registry.yarnpkg.com/bcryptjs/-/bcryptjs-2.4.3.tgz#9ab5627b93e60621ff7cdac5da9733027df1d0cb" integrity sha1-mrVie5PmBiH/fNrF2pczAn3x0Ms= before-after-hook@^2.2.0: version "2.2.2" resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-2.2.2.tgz#a6e8ca41028d90ee2c24222f201c90956091613e" integrity sha512-3pZEU3NT5BFUo/AD5ERPWOgQOCZITni6iavr5AUw5AUwQjMlI0kzu5btnyD39AF0gUEsDPwJT+oY1ORBJijPjQ== big.js@^5.2.2: version "5.2.2" resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ== binary-extensions@^1.0.0: version "1.13.1" resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65" integrity sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw== binary-extensions@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== bindings@^1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ== dependencies: file-uri-to-path "1.0.0" binjumper@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/binjumper/-/binjumper-0.1.4.tgz#4acc0566832714bd6508af6d666bd9e5e21fc7f8" integrity sha512-Gdxhj+U295tIM6cO4bJO1jsvSjBVHNpj2o/OwW7pqDEtaqF6KdOxjtbo93jMMKAkP7+u09+bV8DhSqjIv4qR3w== bl@^4.0.3, bl@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== dependencies: buffer "^5.5.0" inherits "^2.0.4" readable-stream "^3.4.0" bluebird@^3.5.5: version "3.7.2" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.11.9: version "4.12.0" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== bn.js@^5.0.0, bn.js@^5.1.1: version "5.2.0" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.0.tgz#358860674396c6997771a9d051fcc1b57d4ae002" integrity sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw== body-parser@1.19.0, body-parser@^1.19.0: version "1.19.0" resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a" integrity sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw== dependencies: bytes "3.1.0" content-type "~1.0.4" debug "2.6.9" depd "~1.1.2" http-errors "1.7.2" iconv-lite "0.4.24" on-finished "~2.3.0" qs "6.7.0" raw-body "2.4.0" type-is "~1.6.17" boolbase@^1.0.0, boolbase@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24= boolean@^3.0.1: version "3.1.2" resolved "https://registry.yarnpkg.com/boolean/-/boolean-3.1.2.tgz#e30f210a26b02458482a8cc353ab06f262a780c2" integrity sha512-YN6UmV0FfLlBVvRvNPx3pz5W/mUoYB24J4WSXOKP/OOJpi+Oq6WYqPaNTHzjI0QzwWtnvEd5CGYyQPgp1jFxnw== bottleneck@2.19.5: version "2.19.5" resolved "https://registry.yarnpkg.com/bottleneck/-/bottleneck-2.19.5.tgz#5df0b90f59fd47656ebe63c78a98419205cadd91" integrity sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw== boxen@^5.0.0: version "5.0.1" resolved "https://registry.yarnpkg.com/boxen/-/boxen-5.0.1.tgz#657528bdd3f59a772b8279b831f27ec2c744664b" integrity sha512-49VBlw+PrWEF51aCmy7QIteYPIFZxSpvqBdP/2itCPPlJ49kj9zg/XPRFrdkne2W+CfwXUls8exMvu1RysZpKA== dependencies: ansi-align "^3.0.0" camelcase "^6.2.0" chalk "^4.1.0" cli-boxes "^2.2.1" string-width "^4.2.0" type-fest "^0.20.2" widest-line "^3.1.0" wrap-ansi "^7.0.0" brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== dependencies: balanced-match "^1.0.0" concat-map "0.0.1" braces@^2.3.1, braces@^2.3.2: version "2.3.2" resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729" integrity sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w== dependencies: arr-flatten "^1.1.0" array-unique "^0.3.2" extend-shallow "^2.0.1" fill-range "^4.0.0" isobject "^3.0.1" repeat-element "^1.1.2" snapdragon "^0.8.1" snapdragon-node "^2.0.1" split-string "^3.0.2" to-regex "^3.0.1" braces@^3.0.1, braces@^3.0.2, braces@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== dependencies: fill-range "^7.0.1" brorand@^1.0.1, brorand@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" integrity sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8= browser-process-hrtime@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626" integrity sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow== browser-stdout@1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== browserify-aes@^1.0.0, browserify-aes@^1.0.4: version "1.2.0" resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.2.0.tgz#326734642f403dabc3003209853bb70ad428ef48" integrity sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA== dependencies: buffer-xor "^1.0.3" cipher-base "^1.0.0" create-hash "^1.1.0" evp_bytestokey "^1.0.3" inherits "^2.0.1" safe-buffer "^5.0.1" browserify-cipher@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/browserify-cipher/-/browserify-cipher-1.0.1.tgz#8d6474c1b870bfdabcd3bcfcc1934a10e94f15f0" integrity sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w== dependencies: browserify-aes "^1.0.4" browserify-des "^1.0.0" evp_bytestokey "^1.0.0" browserify-des@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/browserify-des/-/browserify-des-1.0.2.tgz#3af4f1f59839403572f1c66204375f7a7f703e9c" integrity sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A== dependencies: cipher-base "^1.0.1" des.js "^1.0.0" inherits "^2.0.1" safe-buffer "^5.1.2" browserify-rsa@^4.0.0, browserify-rsa@^4.0.1: version "4.1.0" resolved "https://registry.yarnpkg.com/browserify-rsa/-/browserify-rsa-4.1.0.tgz#b2fd06b5b75ae297f7ce2dc651f918f5be158c8d" integrity sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog== dependencies: bn.js "^5.0.0" randombytes "^2.0.1" browserify-sign@^4.0.0: version "4.2.1" resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.2.1.tgz#eaf4add46dd54be3bb3b36c0cf15abbeba7956c3" integrity sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg== dependencies: bn.js "^5.1.1" browserify-rsa "^4.0.1" create-hash "^1.2.0" create-hmac "^1.1.7" elliptic "^6.5.3" inherits "^2.0.4" parse-asn1 "^5.1.5" readable-stream "^3.6.0" safe-buffer "^5.2.0" browserify-zlib@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/browserify-zlib/-/browserify-zlib-0.1.4.tgz#bb35f8a519f600e0fa6b8485241c979d0141fb2d" integrity sha1-uzX4pRn2AOD6a4SFJByXnQFB+y0= dependencies: pako "~0.2.0" browserify-zlib@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/browserify-zlib/-/browserify-zlib-0.2.0.tgz#2869459d9aa3be245fe8fe2ca1f46e2e7f54d73f" integrity sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA== dependencies: pako "~1.0.5" browserslist@^4.0.0: version "4.16.7" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.16.7.tgz#108b0d1ef33c4af1b587c54f390e7041178e4335" integrity sha512-7I4qVwqZltJ7j37wObBe3SoTz+nS8APaNcrBOlgoirb6/HbEU2XxW/LpUDTCngM6iauwFqmRTuOMfyKnFGY5JA== dependencies: caniuse-lite "^1.0.30001248" colorette "^1.2.2" electron-to-chromium "^1.3.793" escalade "^3.1.1" node-releases "^1.1.73" buffer-crc32@~0.2.3: version "0.2.13" resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI= buffer-equal-constant-time@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" integrity sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk= buffer-from@^1.0.0: version "1.1.2" resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== buffer-xor@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" integrity sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk= buffer@^4.3.0: version "4.9.2" resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.2.tgz#230ead344002988644841ab0244af8c44bbe3ef8" integrity sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg== dependencies: base64-js "^1.0.2" ieee754 "^1.1.4" isarray "^1.0.0" buffer@^5.5.0: version "5.7.1" resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== dependencies: base64-js "^1.3.1" ieee754 "^1.1.13" bufferutil@^4.0.1: version "4.0.3" resolved "https://registry.yarnpkg.com/bufferutil/-/bufferutil-4.0.3.tgz#66724b756bed23cd7c28c4d306d7994f9943cc6b" integrity sha512-yEYTwGndELGvfXsImMBLop58eaGW+YdONi1fNjTINSY98tmMmFijBG6WXgdkfuLNt4imzQNtIE+eBp1PVpMCSw== dependencies: node-gyp-build "^4.2.0" builtin-modules@^3.1.0: version "3.2.0" resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.2.0.tgz#45d5db99e7ee5e6bc4f362e008bf917ab5049887" integrity sha512-lGzLKcioL90C7wMczpkY0n/oART3MbBa8R9OFGE1rJxoVI86u4WAGfEk8Wjv10eKSyTHVGkSo3bvBylCEtk7LA== builtin-status-codes@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" integrity sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug= builtins@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/builtins/-/builtins-1.0.3.tgz#cb94faeb61c8696451db36534e1422f94f0aee88" integrity sha1-y5T662HIaWRR2zZTThQi+U8K7og= byline@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/byline/-/byline-5.0.0.tgz#741c5216468eadc457b03410118ad77de8c1ddb1" integrity sha1-dBxSFkaOrcRXsDQQEYrXfejB3bE= byte-size@^7.0.0: version "7.0.1" resolved "https://registry.yarnpkg.com/byte-size/-/byte-size-7.0.1.tgz#b1daf3386de7ab9d706b941a748dbfc71130dee3" integrity sha512-crQdqyCwhokxwV1UyDzLZanhkugAgft7vt0qbbdt60C6Zf3CAiGmtUCylbtYwrU6loOUw3euGrNtW1J651ot1A== bytes@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" integrity sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg= bytes@3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg== cacache@^12.0.2: version "12.0.4" resolved "https://registry.yarnpkg.com/cacache/-/cacache-12.0.4.tgz#668bcbd105aeb5f1d92fe25570ec9525c8faa40c" integrity sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ== dependencies: bluebird "^3.5.5" chownr "^1.1.1" figgy-pudding "^3.5.1" glob "^7.1.4" graceful-fs "^4.1.15" infer-owner "^1.0.3" lru-cache "^5.1.1" mississippi "^3.0.0" mkdirp "^0.5.1" move-concurrently "^1.0.1" promise-inflight "^1.0.1" rimraf "^2.6.3" ssri "^6.0.1" unique-filename "^1.1.1" y18n "^4.0.0" cacache@^15.0.5, cacache@^15.2.0: version "15.2.0" resolved "https://registry.yarnpkg.com/cacache/-/cacache-15.2.0.tgz#73af75f77c58e72d8c630a7a2858cb18ef523389" integrity sha512-uKoJSHmnrqXgthDFx/IU6ED/5xd+NNGe+Bb+kLZy7Ku4P+BaiWEUflAKPZ7eAzsYGcsAGASJZsybXp+quEcHTw== dependencies: "@npmcli/move-file" "^1.0.1" chownr "^2.0.0" fs-minipass "^2.0.0" glob "^7.1.4" infer-owner "^1.0.4" lru-cache "^6.0.0" minipass "^3.1.1" minipass-collect "^1.0.2" minipass-flush "^1.0.5" minipass-pipeline "^1.2.2" mkdirp "^1.0.3" p-map "^4.0.0" promise-inflight "^1.0.1" rimraf "^3.0.2" ssri "^8.0.1" tar "^6.0.2" unique-filename "^1.1.1" cache-base@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" integrity sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ== dependencies: collection-visit "^1.0.0" component-emitter "^1.2.1" get-value "^2.0.6" has-value "^1.0.0" isobject "^3.0.1" set-value "^2.0.0" to-object-path "^0.3.0" union-value "^1.0.0" unset-value "^1.0.0" cacheable-lookup@^5.0.3: version "5.0.4" resolved "https://registry.yarnpkg.com/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz#5a6b865b2c44357be3d5ebc2a467b032719a7005" integrity sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA== cacheable-request@^6.0.0: version "6.1.0" resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-6.1.0.tgz#20ffb8bd162ba4be11e9567d823db651052ca912" integrity sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg== dependencies: clone-response "^1.0.2" get-stream "^5.1.0" http-cache-semantics "^4.0.0" keyv "^3.0.0" lowercase-keys "^2.0.0" normalize-url "^4.1.0" responselike "^1.0.2" cacheable-request@^7.0.1: version "7.0.2" resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-7.0.2.tgz#ea0d0b889364a25854757301ca12b2da77f91d27" integrity sha512-pouW8/FmiPQbuGpkXQ9BAPv/Mo5xDGANgSNXzTzJ8DrKGuXOssM4wIQRjfanNRh3Yu5cfYPvcorqbhg2KIJtew== dependencies: clone-response "^1.0.2" get-stream "^5.1.0" http-cache-semantics "^4.0.0" keyv "^4.0.0" lowercase-keys "^2.0.0" normalize-url "^6.0.1" responselike "^2.0.0" call-bind@^1.0.0, call-bind@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== dependencies: function-bind "^1.1.1" get-intrinsic "^1.0.2" caller-callsite@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/caller-callsite/-/caller-callsite-2.0.0.tgz#847e0fce0a223750a9a027c54b33731ad3154134" integrity sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ= dependencies: callsites "^2.0.0" caller-path@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-2.0.0.tgz#468f83044e369ab2010fac5f06ceee15bb2cb1f4" integrity sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ= dependencies: caller-callsite "^2.0.0" callsites@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/callsites/-/callsites-2.0.0.tgz#06eb84f00eea413da86affefacbffb36093b3c50" integrity sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA= callsites@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== camelcase-keys@^6.2.2: version "6.2.2" resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-6.2.2.tgz#5e755d6ba51aa223ec7d3d52f25778210f9dc3c0" integrity sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg== dependencies: camelcase "^5.3.1" map-obj "^4.0.0" quick-lru "^4.0.1" camelcase@^5.0.0, camelcase@^5.3.1: version "5.3.1" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== camelcase@^6.0.0, camelcase@^6.2.0: version "6.2.0" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.2.0.tgz#924af881c9d525ac9d87f40d964e5cea982a1809" integrity sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg== caniuse-api@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-3.0.0.tgz#5e4d90e2274961d46291997df599e3ed008ee4c0" integrity sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw== dependencies: browserslist "^4.0.0" caniuse-lite "^1.0.0" lodash.memoize "^4.1.2" lodash.uniq "^4.5.0" caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001248: version "1.0.30001249" resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001249.tgz#90a330057f8ff75bfe97a94d047d5e14fabb2ee8" integrity sha512-vcX4U8lwVXPdqzPWi6cAJ3FnQaqXbBqy/GZseKNQzRj37J7qZdGcBtxq/QLFNLLlfsoXLUdHw8Iwenri86Tagw== caseless@~0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= chai@^4.3.4: version "4.3.4" resolved "https://registry.yarnpkg.com/chai/-/chai-4.3.4.tgz#b55e655b31e1eac7099be4c08c21964fce2e6c49" integrity sha512-yS5H68VYOCtN1cjfwumDSuzn/9c+yza4f3reKXlE5rUg7SFcCEy90gJvydNgOYtblyf4Zi6jIWRnXOgErta0KA== dependencies: assertion-error "^1.1.0" check-error "^1.0.2" deep-eql "^3.0.1" get-func-name "^2.0.0" pathval "^1.1.1" type-detect "^4.0.5" chalk@4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.1.tgz#c80b3fab28bf6371e6863325eee67e618b77e6ad" integrity sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg== dependencies: ansi-styles "^4.1.0" supports-color "^7.1.0" chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.4.1, chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== dependencies: ansi-styles "^3.2.1" escape-string-regexp "^1.0.5" supports-color "^5.3.0" chalk@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4" integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg== dependencies: ansi-styles "^4.1.0" supports-color "^7.1.0" chalk@^4.0.0, chalk@^4.1.0: version "4.1.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== dependencies: ansi-styles "^4.1.0" supports-color "^7.1.0" chardet@^0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== check-error@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82" integrity sha1-V00xLt2Iu13YkS6Sht1sCu1KrII= child-process@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/child-process/-/child-process-1.0.2.tgz#98974dc7ed1ee4c6229f8e305fa7313a6885a7f2" integrity sha1-mJdNx+0e5MYin44wX6cxOmiFp/I= child_process@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/child_process/-/child_process-1.0.2.tgz#b1f7e7fc73d25e7fd1d455adc94e143830182b5a" integrity sha1-sffn/HPSXn/R1FWtyU4UODAYK1o= chokidar@3.5.2, chokidar@^3.4.1, chokidar@^3.5.1: version "3.5.2" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.2.tgz#dba3976fcadb016f66fd365021d91600d01c1e75" integrity sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ== dependencies: anymatch "~3.1.2" braces "~3.0.2" glob-parent "~5.1.2" is-binary-path "~2.1.0" is-glob "~4.0.1" normalize-path "~3.0.0" readdirp "~3.6.0" optionalDependencies: fsevents "~2.3.2" chokidar@^2.1.8: version "2.1.8" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917" integrity sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg== dependencies: anymatch "^2.0.0" async-each "^1.0.1" braces "^2.3.2" glob-parent "^3.1.0" inherits "^2.0.3" is-binary-path "^1.0.0" is-glob "^4.0.0" normalize-path "^3.0.0" path-is-absolute "^1.0.0" readdirp "^2.2.1" upath "^1.1.1" optionalDependencies: fsevents "^1.2.7" chownr@^1.1.1, chownr@^1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== chownr@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== chrome-trace-event@^1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz#1015eced4741e15d06664a957dbbf50d041e26ac" integrity sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg== ci-info@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: version "1.0.4" resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de" integrity sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q== dependencies: inherits "^2.0.1" safe-buffer "^5.0.1" class-utils@^0.3.5: version "0.3.6" resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" integrity sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg== dependencies: arr-union "^3.1.0" define-property "^0.2.5" isobject "^3.0.0" static-extend "^0.1.1" clean-stack@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== cli-boxes@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-2.2.1.tgz#ddd5035d25094fce220e9cab40a45840a440318f" integrity sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw== cli-cursor@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw== dependencies: restore-cursor "^3.1.0" cli-spinner@0.2.10: version "0.2.10" resolved "https://registry.yarnpkg.com/cli-spinner/-/cli-spinner-0.2.10.tgz#f7d617a36f5c47a7bc6353c697fc9338ff782a47" integrity sha512-U0sSQ+JJvSLi1pAYuJykwiA8Dsr15uHEy85iCJ6A+0DjVxivr3d+N2Wjvodeg89uP5K6TswFkKBfAD7B3YSn/Q== cli-spinners@^2.5.0: version "2.6.0" resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.6.0.tgz#36c7dc98fb6a9a76bd6238ec3f77e2425627e939" integrity sha512-t+4/y50K/+4xcCRosKkA7W4gTr1MySvLV0q+PxmG7FJ5g+66ChKurYjxBCjHggHH3HA5Hh9cy+lcUGWDqVH+4Q== cli-truncate@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-2.1.0.tgz#c39e28bf05edcde5be3b98992a22deed5a2b93c7" integrity sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg== dependencies: slice-ansi "^3.0.0" string-width "^4.2.0" cli-width@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-3.0.0.tgz#a2f48437a2caa9a22436e794bf071ec9e61cedf6" integrity sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw== clipanion@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/clipanion/-/clipanion-3.0.0.tgz#efe35d17f46d9cf71be5eb11c36e9a05caa6cf68" integrity sha512-Ae2/PVkgMM/EbiGugE0p0QcjXyuh5hP+yzpJYaIElQ0g0kkuQKEHuYkLyk4K8gNkO1KKlHZ6UWgxRaTOMtmiLw== dependencies: typanion "^3.3.1" clipanion@^2.6.2: version "2.6.2" resolved "https://registry.yarnpkg.com/clipanion/-/clipanion-2.6.2.tgz#820e7440812052442455b248f927b187ed732f71" integrity sha512-0tOHJNMF9+4R3qcbBL+4IxLErpaYSYvzs10aXuECDbZdJOuJHdagJMAqvLdeaUQTI/o2uSCDRpet6ywDiKOAYw== cliui@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5" integrity sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA== dependencies: string-width "^3.1.0" strip-ansi "^5.2.0" wrap-ansi "^5.1.0" cliui@^7.0.2: version "7.0.4" resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== dependencies: string-width "^4.2.0" strip-ansi "^6.0.0" wrap-ansi "^7.0.0" clone-deep@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387" integrity sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ== dependencies: is-plain-object "^2.0.4" kind-of "^6.0.2" shallow-clone "^3.0.0" clone-response@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b" integrity sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws= dependencies: mimic-response "^1.0.0" clone@^1.0.2: version "1.0.4" resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" integrity sha1-2jCcwmPfFZlMaIypAheco8fNfH4= cmd-shim@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/cmd-shim/-/cmd-shim-4.1.0.tgz#b3a904a6743e9fede4148c6f3800bf2a08135bdd" integrity sha512-lb9L7EM4I/ZRVuljLPEtUJOP+xiQVknZ4ZMpMgEp4JzNldPb27HU03hi6K1/6CoIuit/Zm/LQXySErFeXxDprw== dependencies: mkdirp-infer-owner "^2.0.0" coa@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/coa/-/coa-2.0.2.tgz#43f6c21151b4ef2bf57187db0d73de229e3e7ec3" integrity sha512-q5/jG+YQnSy4nRTV4F7lPepBJZ8qBNJJDBuJdoejDyLXgmL7IEo+Le2JDZudFTFt7mrCqIRaSjws4ygRCTCAXA== dependencies: "@types/q" "^1.5.1" chalk "^2.4.1" q "^1.1.2" code-point-at@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= codemirror@^5.48.2: version "5.62.2" resolved "https://registry.yarnpkg.com/codemirror/-/codemirror-5.62.2.tgz#bce6d19c9829e6e788f83886d48ecf5c1e106e65" integrity sha512-tVFMUa4J3Q8JUd1KL9yQzQB0/BJt7ZYZujZmTPgo/54Lpuq3ez4C8x/ATUY/wv7b7X3AUq8o3Xd+2C5ZrCGWHw== collection-visit@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" integrity sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA= dependencies: map-visit "^1.0.0" object-visit "^1.0.0" color-convert@^1.9.0, color-convert@^1.9.3: version "1.9.3" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== dependencies: color-name "1.1.3" color-convert@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== dependencies: color-name "~1.1.4" color-name@1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= color-name@^1.0.0, color-name@~1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== color-string@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.6.0.tgz#c3915f61fe267672cb7e1e064c9d692219f6c312" integrity sha512-c/hGS+kRWJutUBEngKKmk4iH3sD59MBkoxVapS/0wgpCz2u7XsNloxknyvBhzwEs1IbV36D9PwqLPJ2DTu3vMA== dependencies: color-name "^1.0.0" simple-swizzle "^0.2.2" color@^3.0.0: version "3.2.1" resolved "https://registry.yarnpkg.com/color/-/color-3.2.1.tgz#3544dc198caf4490c3ecc9a790b54fe9ff45e164" integrity sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA== dependencies: color-convert "^1.9.3" color-string "^1.6.0" colorette@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.2.tgz#cbcc79d5e99caea2dbf10eb3a26fd8b3e6acfa94" integrity sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w== colorette@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.4.0.tgz#5190fbb87276259a86ad700bff2c6d6faa3fca40" integrity sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g== colors@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== colors@~1.2.1: version "1.2.5" resolved "https://registry.yarnpkg.com/colors/-/colors-1.2.5.tgz#89c7ad9a374bc030df8013241f68136ed8835afc" integrity sha512-erNRLao/Y3Fv54qUa0LBB+//Uf3YwMUmdJinN20yMXm9zdKKqH9wt7R9IIVZ+K7ShzfpLV/Zg8+VyrBJYB4lpg== columnify@^1.5.4: version "1.5.4" resolved "https://registry.yarnpkg.com/columnify/-/columnify-1.5.4.tgz#4737ddf1c7b69a8a7c340570782e947eec8e78bb" integrity sha1-Rzfd8ce2mop8NAVweC6UfuyOeLs= dependencies: strip-ansi "^3.0.0" wcwidth "^1.0.0" combined-stream@^1.0.6, combined-stream@~1.0.6: version "1.0.8" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== dependencies: delayed-stream "~1.0.0" commander@^2.20.0, commander@^2.7.1: version "2.20.3" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== commander@^6.2.0: version "6.2.1" resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c" integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA== commander@~6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/commander/-/commander-6.0.0.tgz#2b270da94f8fb9014455312f829a1129dbf8887e" integrity sha512-s7EA+hDtTYNhuXkTlhqew4txMZVdszBmKWSPEMxGr8ru8JXR7bLUFIAtPhcSuFdJQ0ILMxnJi8GkQL0yvDy/YA== commondir@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs= compare-func@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/compare-func/-/compare-func-2.0.0.tgz#fb65e75edbddfd2e568554e8b5b05fff7a51fcb3" integrity sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA== dependencies: array-ify "^1.0.0" dot-prop "^5.1.0" compare-versions@^3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-3.6.0.tgz#1a5689913685e5a87637b8d3ffca75514ec41d62" integrity sha512-W6Af2Iw1z4CB7q4uU4hv646dW9GQuBM+YpC0UvUCWSD8w90SJjp+ujJuXaEMtAXBtSqGfMPuFOVn4/+FlaqfBA== component-emitter@^1.2.1, component-emitter@~1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== compressible@~2.0.16: version "2.0.18" resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba" integrity sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg== dependencies: mime-db ">= 1.43.0 < 2" compression@1.7.4: version "1.7.4" resolved "https://registry.yarnpkg.com/compression/-/compression-1.7.4.tgz#95523eff170ca57c29a0ca41e6fe131f41e5bb8f" integrity sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ== dependencies: accepts "~1.3.5" bytes "3.0.0" compressible "~2.0.16" debug "2.6.9" on-headers "~1.0.2" safe-buffer "5.1.2" vary "~1.1.2" concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= concat-stream@^1.5.0, concat-stream@^1.5.2, concat-stream@^1.6.2: version "1.6.2" resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== dependencies: buffer-from "^1.0.0" inherits "^2.0.3" readable-stream "^2.2.2" typedarray "^0.0.6" concat-stream@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-2.0.0.tgz#414cf5af790a48c60ab9be4527d56d5e41133cb1" integrity sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A== dependencies: buffer-from "^1.0.0" inherits "^2.0.3" readable-stream "^3.0.2" typedarray "^0.0.6" concat-with-sourcemaps@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/concat-with-sourcemaps/-/concat-with-sourcemaps-1.1.0.tgz#d4ea93f05ae25790951b99e7b3b09e3908a4082e" integrity sha512-4gEjHJFT9e+2W/77h/DS5SGUgwDaOwprX8L/gl5+3ixnzkVJJsZWDSelmN3Oilw3LNDZjZV0yqH1hLG3k6nghg== dependencies: source-map "^0.6.1" config-chain@^1.1.12: version "1.1.13" resolved "https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.13.tgz#fad0795aa6a6cdaff9ed1b68e9dff94372c232f4" integrity sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ== dependencies: ini "^1.3.4" proto-list "~1.2.1" configstore@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/configstore/-/configstore-5.0.1.tgz#d365021b5df4b98cdd187d6a3b0e3f6a7cc5ed96" integrity sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA== dependencies: dot-prop "^5.2.0" graceful-fs "^4.1.2" make-dir "^3.0.0" unique-string "^2.0.0" write-file-atomic "^3.0.0" xdg-basedir "^4.0.0" connect@^3.7.0: version "3.7.0" resolved "https://registry.yarnpkg.com/connect/-/connect-3.7.0.tgz#5d49348910caa5e07a01800b030d0c35f20484f8" integrity sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ== dependencies: debug "2.6.9" finalhandler "1.1.2" parseurl "~1.3.3" utils-merge "1.0.1" console-browserify@^1.1.0: version "1.2.0" resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.2.0.tgz#67063cef57ceb6cf4993a2ab3a55840ae8c49336" integrity sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA== console-control-strings@^1.0.0, console-control-strings@~1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= constants-browserify@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75" integrity sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U= content-disposition@0.5.3: version "0.5.3" resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd" integrity sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g== dependencies: safe-buffer "5.1.2" content-type@~1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== conventional-changelog-angular@^5.0.12: version "5.0.12" resolved "https://registry.yarnpkg.com/conventional-changelog-angular/-/conventional-changelog-angular-5.0.12.tgz#c979b8b921cbfe26402eb3da5bbfda02d865a2b9" integrity sha512-5GLsbnkR/7A89RyHLvvoExbiGbd9xKdKqDTrArnPbOqBqG/2wIosu0fHwpeIRI8Tl94MhVNBXcLJZl92ZQ5USw== dependencies: compare-func "^2.0.0" q "^1.5.1" conventional-changelog-core@^4.2.2: version "4.2.3" resolved "https://registry.yarnpkg.com/conventional-changelog-core/-/conventional-changelog-core-4.2.3.tgz#ce44d4bbba4032e3dc14c00fcd5b53fc00b66433" integrity sha512-MwnZjIoMRL3jtPH5GywVNqetGILC7g6RQFvdb8LRU/fA/338JbeWAku3PZ8yQ+mtVRViiISqJlb0sOz0htBZig== dependencies: add-stream "^1.0.0" conventional-changelog-writer "^5.0.0" conventional-commits-parser "^3.2.0" dateformat "^3.0.0" get-pkg-repo "^4.0.0" git-raw-commits "^2.0.8" git-remote-origin-url "^2.0.0" git-semver-tags "^4.1.1" lodash "^4.17.15" normalize-package-data "^3.0.0" q "^1.5.1" read-pkg "^3.0.0" read-pkg-up "^3.0.0" through2 "^4.0.0" conventional-changelog-preset-loader@^2.3.4: version "2.3.4" resolved "https://registry.yarnpkg.com/conventional-changelog-preset-loader/-/conventional-changelog-preset-loader-2.3.4.tgz#14a855abbffd59027fd602581f1f34d9862ea44c" integrity sha512-GEKRWkrSAZeTq5+YjUZOYxdHq+ci4dNwHvpaBC3+ENalzFWuCWa9EZXSuZBpkr72sMdKB+1fyDV4takK1Lf58g== conventional-changelog-writer@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/conventional-changelog-writer/-/conventional-changelog-writer-5.0.0.tgz#c4042f3f1542f2f41d7d2e0d6cad23aba8df8eec" integrity sha512-HnDh9QHLNWfL6E1uHz6krZEQOgm8hN7z/m7tT16xwd802fwgMN0Wqd7AQYVkhpsjDUx/99oo+nGgvKF657XP5g== dependencies: conventional-commits-filter "^2.0.7" dateformat "^3.0.0" handlebars "^4.7.6" json-stringify-safe "^5.0.1" lodash "^4.17.15" meow "^8.0.0" semver "^6.0.0" split "^1.0.0" through2 "^4.0.0" conventional-commits-filter@^2.0.7: version "2.0.7" resolved "https://registry.yarnpkg.com/conventional-commits-filter/-/conventional-commits-filter-2.0.7.tgz#f8d9b4f182fce00c9af7139da49365b136c8a0b3" integrity sha512-ASS9SamOP4TbCClsRHxIHXRfcGCnIoQqkvAzCSbZzTFLfcTqJVugB0agRgsEELsqaeWgsXv513eS116wnlSSPA== dependencies: lodash.ismatch "^4.4.0" modify-values "^1.0.0" conventional-commits-parser@^3.2.0: version "3.2.1" resolved "https://registry.yarnpkg.com/conventional-commits-parser/-/conventional-commits-parser-3.2.1.tgz#ba44f0b3b6588da2ee9fd8da508ebff50d116ce2" integrity sha512-OG9kQtmMZBJD/32NEw5IhN5+HnBqVjy03eC+I71I0oQRFA5rOgA4OtPOYG7mz1GkCfCNxn3gKIX8EiHJYuf1cA== dependencies: JSONStream "^1.0.4" is-text-path "^1.0.1" lodash "^4.17.15" meow "^8.0.0" split2 "^3.0.0" through2 "^4.0.0" trim-off-newlines "^1.0.0" conventional-recommended-bump@^6.1.0: version "6.1.0" resolved "https://registry.yarnpkg.com/conventional-recommended-bump/-/conventional-recommended-bump-6.1.0.tgz#cfa623285d1de554012f2ffde70d9c8a22231f55" integrity sha512-uiApbSiNGM/kkdL9GTOLAqC4hbptObFo4wW2QRyHsKciGAfQuLU1ShZ1BIVI/+K2BE/W1AWYQMCXAsv4dyKPaw== dependencies: concat-stream "^2.0.0" conventional-changelog-preset-loader "^2.3.4" conventional-commits-filter "^2.0.7" conventional-commits-parser "^3.2.0" git-raw-commits "^2.0.8" git-semver-tags "^4.1.1" meow "^8.0.0" q "^1.5.1" cookie-signature@1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw= cookie@0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba" integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg== cookie@~0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.1.tgz#afd713fe26ebd21ba95ceb61f9a8116e50a537d1" integrity sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA== cookies@0.8.0: version "0.8.0" resolved "https://registry.yarnpkg.com/cookies/-/cookies-0.8.0.tgz#1293ce4b391740a8406e3c9870e828c4b54f3f90" integrity sha512-8aPsApQfebXnuI+537McwYsDtjVxGm8gTIzQI3FDW6t5t/DAhERxtnbEPN/8RX+uZthoz4eCOgloXaE5cYyNow== dependencies: depd "~2.0.0" keygrip "~1.1.0" copy-concurrently@^1.0.0: version "1.0.5" resolved "https://registry.yarnpkg.com/copy-concurrently/-/copy-concurrently-1.0.5.tgz#92297398cae34937fcafd6ec8139c18051f0b5e0" integrity sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A== dependencies: aproba "^1.1.1" fs-write-stream-atomic "^1.0.8" iferr "^0.1.5" mkdirp "^0.5.1" rimraf "^2.5.4" run-queue "^1.0.0" copy-descriptor@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= core-js@^3.6.5: version "3.16.0" resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.16.0.tgz#1d46fb33720bc1fa7f90d20431f36a5540858986" integrity sha512-5+5VxRFmSf97nM8Jr2wzOwLqRo6zphH2aX+7KsAUONObyzakDNq2G/bgbhinxB4PoV9L3aXQYhiDKyIKWd2c8g== core-util-is@1.0.2, core-util-is@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= cors@2.8.5, cors@~2.8.5: version "2.8.5" resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29" integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g== dependencies: object-assign "^4" vary "^1" cosmiconfig@^5.0.0: version "5.2.1" resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-5.2.1.tgz#040f726809c591e77a17c0a3626ca45b4f168b1a" integrity sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA== dependencies: import-fresh "^2.0.0" is-directory "^0.3.1" js-yaml "^3.13.1" parse-json "^4.0.0" cosmiconfig@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.0.0.tgz#ef9b44d773959cae63ddecd122de23853b60f8d3" integrity sha512-pondGvTuVYDk++upghXJabWzL6Kxu6f26ljFw64Swq9v6sQPUL3EUlVDV56diOjpCayKihL6hVe8exIACU4XcA== dependencies: "@types/parse-json" "^4.0.0" import-fresh "^3.2.1" parse-json "^5.0.0" path-type "^4.0.0" yaml "^1.10.0" create-ecdh@^4.0.0: version "4.0.4" resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.4.tgz#d6e7f4bffa66736085a0762fd3a632684dabcc4e" integrity sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A== dependencies: bn.js "^4.1.0" elliptic "^6.5.3" create-hash@^1.1.0, create-hash@^1.1.2, create-hash@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196" integrity sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg== dependencies: cipher-base "^1.0.1" inherits "^2.0.1" md5.js "^1.3.4" ripemd160 "^2.0.1" sha.js "^2.4.0" create-hmac@^1.1.0, create-hmac@^1.1.4, create-hmac@^1.1.7: version "1.1.7" resolved "https://registry.yarnpkg.com/create-hmac/-/create-hmac-1.1.7.tgz#69170c78b3ab957147b2b8b04572e47ead2243ff" integrity sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg== dependencies: cipher-base "^1.0.3" create-hash "^1.1.0" inherits "^2.0.1" ripemd160 "^2.0.0" safe-buffer "^5.0.1" sha.js "^2.4.8" cross-spawn@7.0.3, cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== dependencies: path-key "^3.1.0" shebang-command "^2.0.0" which "^2.0.1" cross-spawn@^6.0.0, cross-spawn@^6.0.5: version "6.0.5" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== dependencies: nice-try "^1.0.4" path-key "^2.0.1" semver "^5.5.0" shebang-command "^1.2.0" which "^1.2.9" crypto-browserify@^3.11.0: version "3.12.0" resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec" integrity sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg== dependencies: browserify-cipher "^1.0.0" browserify-sign "^4.0.0" create-ecdh "^4.0.0" create-hash "^1.1.0" create-hmac "^1.1.0" diffie-hellman "^5.0.0" inherits "^2.0.1" pbkdf2 "^3.0.3" public-encrypt "^4.0.0" randombytes "^2.0.0" randomfill "^1.0.3" crypto-random-string@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA== crypto@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/crypto/-/crypto-1.0.1.tgz#2af1b7cad8175d24c8a1b0778255794a21803037" integrity sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig== css-color-names@0.0.4, css-color-names@^0.0.4: version "0.0.4" resolved "https://registry.yarnpkg.com/css-color-names/-/css-color-names-0.0.4.tgz#808adc2e79cf84738069b646cb20ec27beb629e0" integrity sha1-gIrcLnnPhHOAabZGyyDsJ762KeA= css-declaration-sorter@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/css-declaration-sorter/-/css-declaration-sorter-4.0.1.tgz#c198940f63a76d7e36c1e71018b001721054cb22" integrity sha512-BcxQSKTSEEQUftYpBVnsH4SF05NTuBokb19/sBt6asXGKZ/6VP7PLG1CBCkFDYOnhXhPh0jMhO6xZ71oYHXHBA== dependencies: postcss "^7.0.1" timsort "^0.3.0" css-loader@^3.4.0: version "3.6.0" resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-3.6.0.tgz#2e4b2c7e6e2d27f8c8f28f61bffcd2e6c91ef645" integrity sha512-M5lSukoWi1If8dhQAUCvj4H8vUt3vOnwbQBH9DdTm/s4Ym2B/3dPMtYZeJmq7Q3S3Pa+I94DcZ7pc9bP14cWIQ== dependencies: camelcase "^5.3.1" cssesc "^3.0.0" icss-utils "^4.1.1" loader-utils "^1.2.3" normalize-path "^3.0.0" postcss "^7.0.32" postcss-modules-extract-imports "^2.0.0" postcss-modules-local-by-default "^3.0.2" postcss-modules-scope "^2.2.0" postcss-modules-values "^3.0.0" postcss-value-parser "^4.1.0" schema-utils "^2.7.0" semver "^6.3.0" css-select-base-adapter@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz#3b2ff4972cc362ab88561507a95408a1432135d7" integrity sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w== css-select@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/css-select/-/css-select-2.1.0.tgz#6a34653356635934a81baca68d0255432105dbef" integrity sha512-Dqk7LQKpwLoH3VovzZnkzegqNSuAziQyNZUcrdDM401iY+R5NkGBXGmtO05/yaXQziALuPogeG0b7UAgjnTJTQ== dependencies: boolbase "^1.0.0" css-what "^3.2.1" domutils "^1.7.0" nth-check "^1.0.2" css-tree@1.0.0-alpha.37: version "1.0.0-alpha.37" resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.0.0-alpha.37.tgz#98bebd62c4c1d9f960ec340cf9f7522e30709a22" integrity sha512-DMxWJg0rnz7UgxKT0Q1HU/L9BeJI0M6ksor0OgqOnF+aRCDWg/N2641HmVyU9KVIu0OVVWOb2IpC9A+BJRnejg== dependencies: mdn-data "2.0.4" source-map "^0.6.1" css-tree@^1.1.2: version "1.1.3" resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.1.3.tgz#eb4870fb6fd7707327ec95c2ff2ab09b5e8db91d" integrity sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q== dependencies: mdn-data "2.0.14" source-map "^0.6.1" css-what@^3.2.1: version "3.4.2" resolved "https://registry.yarnpkg.com/css-what/-/css-what-3.4.2.tgz#ea7026fcb01777edbde52124e21f327e7ae950e4" integrity sha512-ACUm3L0/jiZTqfzRM3Hi9Q8eZqd6IK37mMWPLz9PJxkLWllYeRf+EHUSHYEtFop2Eqytaq1FizFVh7XfBnXCDQ== cssesc@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== cssnano-preset-default@^4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-4.0.8.tgz#920622b1fc1e95a34e8838203f1397a504f2d3ff" integrity sha512-LdAyHuq+VRyeVREFmuxUZR1TXjQm8QQU/ktoo/x7bz+SdOge1YKc5eMN6pRW7YWBmyq59CqYba1dJ5cUukEjLQ== dependencies: css-declaration-sorter "^4.0.1" cssnano-util-raw-cache "^4.0.1" postcss "^7.0.0" postcss-calc "^7.0.1" postcss-colormin "^4.0.3" postcss-convert-values "^4.0.1" postcss-discard-comments "^4.0.2" postcss-discard-duplicates "^4.0.2" postcss-discard-empty "^4.0.1" postcss-discard-overridden "^4.0.1" postcss-merge-longhand "^4.0.11" postcss-merge-rules "^4.0.3" postcss-minify-font-values "^4.0.2" postcss-minify-gradients "^4.0.2" postcss-minify-params "^4.0.2" postcss-minify-selectors "^4.0.2" postcss-normalize-charset "^4.0.1" postcss-normalize-display-values "^4.0.2" postcss-normalize-positions "^4.0.2" postcss-normalize-repeat-style "^4.0.2" postcss-normalize-string "^4.0.2" postcss-normalize-timing-functions "^4.0.2" postcss-normalize-unicode "^4.0.1" postcss-normalize-url "^4.0.1" postcss-normalize-whitespace "^4.0.2" postcss-ordered-values "^4.1.2" postcss-reduce-initial "^4.0.3" postcss-reduce-transforms "^4.0.2" postcss-svgo "^4.0.3" postcss-unique-selectors "^4.0.1" cssnano-util-get-arguments@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/cssnano-util-get-arguments/-/cssnano-util-get-arguments-4.0.0.tgz#ed3a08299f21d75741b20f3b81f194ed49cc150f" integrity sha1-7ToIKZ8h11dBsg87gfGU7UnMFQ8= cssnano-util-get-match@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/cssnano-util-get-match/-/cssnano-util-get-match-4.0.0.tgz#c0e4ca07f5386bb17ec5e52250b4f5961365156d" integrity sha1-wOTKB/U4a7F+xeUiULT1lhNlFW0= cssnano-util-raw-cache@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/cssnano-util-raw-cache/-/cssnano-util-raw-cache-4.0.1.tgz#b26d5fd5f72a11dfe7a7846fb4c67260f96bf282" integrity sha512-qLuYtWK2b2Dy55I8ZX3ky1Z16WYsx544Q0UWViebptpwn/xDBmog2TLg4f+DBMg1rJ6JDWtn96WHbOKDWt1WQA== dependencies: postcss "^7.0.0" cssnano-util-same-parent@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/cssnano-util-same-parent/-/cssnano-util-same-parent-4.0.1.tgz#574082fb2859d2db433855835d9a8456ea18bbf3" integrity sha512-WcKx5OY+KoSIAxBW6UBBRay1U6vkYheCdjyVNDm85zt5K9mHoGOfsOsqIszfAqrQQFIIKgjh2+FDgIj/zsl21Q== cssnano@^4.1.10: version "4.1.11" resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-4.1.11.tgz#c7b5f5b81da269cb1fd982cb960c1200910c9a99" integrity sha512-6gZm2htn7xIPJOHY824ERgj8cNPgPxyCSnkXc4v7YvNW+TdVfzgngHcEhy/8D11kUWRUMbke+tC+AUcUsnMz2g== dependencies: cosmiconfig "^5.0.0" cssnano-preset-default "^4.0.8" is-resolvable "^1.0.0" postcss "^7.0.0" csso@^4.0.2: version "4.2.0" resolved "https://registry.yarnpkg.com/csso/-/csso-4.2.0.tgz#ea3a561346e8dc9f546d6febedd50187cf389529" integrity sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA== dependencies: css-tree "^1.1.2" cssom@^0.4.1: version "0.4.4" resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.4.4.tgz#5a66cf93d2d0b661d80bf6a44fb65f5c2e4e0a10" integrity sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw== cssom@~0.3.6: version "0.3.8" resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.8.tgz#9f1276f5b2b463f2114d3f2c75250af8c1a36f4a" integrity sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg== cssstyle@^2.0.0: version "2.3.0" resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-2.3.0.tgz#ff665a0ddbdc31864b09647f34163443d90b0852" integrity sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A== dependencies: cssom "~0.3.6" custom-event@~1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/custom-event/-/custom-event-1.0.1.tgz#5d02a46850adf1b4a317946a3928fccb5bfd0425" integrity sha1-XQKkaFCt8bSjF5RqOSj8y1v9BCU= cyclist@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9" integrity sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk= d@1, d@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/d/-/d-1.0.1.tgz#8698095372d58dbee346ffd0c7093f99f8f9eb5a" integrity sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA== dependencies: es5-ext "^0.10.50" type "^1.0.1" dargs@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/dargs/-/dargs-7.0.0.tgz#04015c41de0bcb69ec84050f3d9be0caf8d6d5cc" integrity sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg== dashdash@^1.12.0: version "1.14.1" resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" integrity sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA= dependencies: assert-plus "^1.0.0" data-urls@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-1.1.0.tgz#15ee0582baa5e22bb59c77140da8f9c76963bbfe" integrity sha512-YTWYI9se1P55u58gL5GkQHW4P6VJBJ5iBT+B5a7i2Tjadhv52paJG0qHX4A0OR6/t52odI64KP2YvFpkDOi3eQ== dependencies: abab "^2.0.0" whatwg-mimetype "^2.2.0" whatwg-url "^7.0.0" date-format@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/date-format/-/date-format-2.1.0.tgz#31d5b5ea211cf5fd764cd38baf9d033df7e125cf" integrity sha512-bYQuGLeFxhkxNOF3rcMtiZxvCBAquGzZm6oWA1oZ0g2THUzivaRhv8uOhdr19LmoobSOLoIAxeUK2RdbM8IFTA== date-format@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/date-format/-/date-format-3.0.0.tgz#eb8780365c7d2b1511078fb491e6479780f3ad95" integrity sha512-eyTcpKOcamdhWJXj56DpQMo1ylSQpcGtGKXcU0Tb97+K56/CF5amAqqqNj0+KvA0iw2ynxtHWFsPDSClCxe48w== dateformat@^3.0.0: version "3.0.3" resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-3.0.3.tgz#a6e37499a4d9a9cf85ef5872044d62901c9889ae" integrity sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q== dayjs@1.10.6: version "1.10.6" resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.10.6.tgz#288b2aa82f2d8418a6c9d4df5898c0737ad02a63" integrity sha512-AztC/IOW4L1Q41A86phW5Thhcrco3xuAA+YX/BLpLWWjRcTj5TOt/QImBLmCKlrF7u7k47arTnOyL6GnbG8Hvw== debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== dependencies: ms "2.0.0" debug@4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.2.0, debug@^4.3.1, debug@^4.3.2, debug@~4.3.1: version "4.3.2" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b" integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw== dependencies: ms "2.1.2" debug@4.3.1: version "4.3.1" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== dependencies: ms "2.1.2" debug@^3.1.0, debug@^3.2.6: version "3.2.7" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== dependencies: ms "^2.1.1" debuglog@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492" integrity sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI= decamelize-keys@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/decamelize-keys/-/decamelize-keys-1.1.0.tgz#d171a87933252807eb3cb61dc1c1445d078df2d9" integrity sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk= dependencies: decamelize "^1.1.0" map-obj "^1.0.0" decamelize@^1.1.0, decamelize@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= decamelize@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-4.0.0.tgz#aa472d7bf660eb15f3494efd531cab7f2a709837" integrity sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ== decode-uri-component@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= decompress-response@^3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-3.3.0.tgz#80a4dd323748384bfa248083622aedec982adff3" integrity sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M= dependencies: mimic-response "^1.0.0" decompress-response@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc" integrity sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ== dependencies: mimic-response "^3.1.0" dedent@^0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c" integrity sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw= deep-eql@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-3.0.1.tgz#dfc9404400ad1c8fe023e7da1df1c147c4b444df" integrity sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw== dependencies: type-detect "^4.0.0" deep-extend@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== deep-is@^0.1.3: version "0.1.4" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== deep-is@~0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= defaults@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.3.tgz#c656051e9817d9ff08ed881477f3fe4019f3ef7d" integrity sha1-xlYFHpgX2f8I7YgUd/P+QBnz730= dependencies: clone "^1.0.2" defer-to-connect@^1.0.1: version "1.1.3" resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-1.1.3.tgz#331ae050c08dcf789f8c83a7b81f0ed94f4ac591" integrity sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ== defer-to-connect@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-2.0.1.tgz#8016bdb4143e4632b77a3449c6236277de520587" integrity sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg== define-properties@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== dependencies: object-keys "^1.0.12" define-property@^0.2.5: version "0.2.5" resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116" integrity sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY= dependencies: is-descriptor "^0.1.0" define-property@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/define-property/-/define-property-1.0.0.tgz#769ebaaf3f4a63aad3af9e8d304c9bbe79bfb0e6" integrity sha1-dp66rz9KY6rTr56NMEybvnm/sOY= dependencies: is-descriptor "^1.0.0" define-property@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/define-property/-/define-property-2.0.2.tgz#d459689e8d654ba77e02a817f8710d702cb16e9d" integrity sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ== dependencies: is-descriptor "^1.0.2" isobject "^3.0.1" del@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/del/-/del-6.0.0.tgz#0b40d0332cea743f1614f818be4feb717714c952" integrity sha512-1shh9DQ23L16oXSZKB2JxpL7iMy2E0S9d517ptA1P8iw0alkPtQcrKH7ru31rYtKwF499HkTu+DRzq3TCKDFRQ== dependencies: globby "^11.0.1" graceful-fs "^4.2.4" is-glob "^4.0.1" is-path-cwd "^2.2.0" is-path-inside "^3.0.2" p-map "^4.0.0" rimraf "^3.0.2" slash "^3.0.0" delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= delegates@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= depd@^1.1.2, depd@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= depd@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== dependency-graph@^0.9.0: version "0.9.0" resolved "https://registry.yarnpkg.com/dependency-graph/-/dependency-graph-0.9.0.tgz#11aed7e203bc8b00f48356d92db27b265c445318" integrity sha512-9YLIBURXj4DJMFALxXw9K3Y3rwb5Fk0X5/8ipCzaN84+gKxoHK43tVKRNakCQbiEx07E8Uwhuq21BpUagFhZ8w== deprecation@^2.0.0, deprecation@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/deprecation/-/deprecation-2.3.1.tgz#6368cbdb40abf3373b525ac87e4a260c3a700919" integrity sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ== des.js@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.1.tgz#5382142e1bdc53f85d86d53e5f4aa7deb91e0843" integrity sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA== dependencies: inherits "^2.0.1" minimalistic-assert "^1.0.0" destroy@~1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= detect-file@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/detect-file/-/detect-file-1.0.0.tgz#f0d66d03672a825cb1b73bdb3fe62310c8e552b7" integrity sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc= detect-indent@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-5.0.0.tgz#3871cc0a6a002e8c3e5b3cf7f336264675f06b9d" integrity sha1-OHHMCmoALow+Wzz38zYmRnXwa50= detect-indent@^6.0.0: version "6.1.0" resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-6.1.0.tgz#592485ebbbf6b3b1ab2be175c8393d04ca0d57e6" integrity sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA== detect-newline@3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== detect-node@^2.0.4: version "2.1.0" resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.1.0.tgz#c9c70775a49c3d03bc2c06d9a73be550f978f8b1" integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g== dezalgo@^1.0.0: version "1.0.3" resolved "https://registry.yarnpkg.com/dezalgo/-/dezalgo-1.0.3.tgz#7f742de066fc748bc8db820569dddce49bf0d456" integrity sha1-f3Qt4Gb8dIvI24IFad3c5Jvw1FY= dependencies: asap "^2.0.0" wrappy "1" di@^0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/di/-/di-0.0.1.tgz#806649326ceaa7caa3306d75d985ea2748ba913c" integrity sha1-gGZJMmzqp8qjMG112YXqJ0i6kTw= diff@5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/diff/-/diff-5.0.0.tgz#7ed6ad76d859d030787ec35855f5b1daf31d852b" integrity sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w== diff@^4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== diffie-hellman@^5.0.0: version "5.0.3" resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875" integrity sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg== dependencies: bn.js "^4.1.0" miller-rabin "^4.0.0" randombytes "^2.0.0" dir-glob@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== dependencies: path-type "^4.0.0" docker-modem@2.1.3: version "2.1.3" resolved "https://registry.yarnpkg.com/docker-modem/-/docker-modem-2.1.3.tgz#15432225f63db02eb5de4bb9a621b7293e5f264d" integrity sha512-cwaRptBmYZwu/FyhGcqBm2MzXA77W2/E6eVkpOZVDk6PkI9Bjj84xPrXiHMA+OWjzNy+DFjgKh8Q+1hMR7/OHg== dependencies: debug "^4.1.1" readable-stream "^3.5.0" split-ca "^1.0.1" ssh2 "^0.8.7" dockerfile-ast@0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/dockerfile-ast/-/dockerfile-ast-0.2.1.tgz#bb7c731e7816522a78c92bbc2e830a2016daae94" integrity sha512-ut04CVM1G6zIITTcYPDIXhPZk9mCa21m4dfW8FcDDGxwgTQhYyHDu6U7M8klZ7QsjqVcJhryKi+TGOX6bjgKdQ== dependencies: vscode-languageserver-types "^3.16.0" doctrine@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== dependencies: esutils "^2.0.2" dom-serialize@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/dom-serialize/-/dom-serialize-2.2.1.tgz#562ae8999f44be5ea3076f5419dcd59eb43ac95b" integrity sha1-ViromZ9Evl6jB29UGdzVnrQ6yVs= dependencies: custom-event "~1.0.0" ent "~2.2.0" extend "^3.0.0" void-elements "^2.0.0" dom-serializer@0: version "0.2.2" resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51" integrity sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g== dependencies: domelementtype "^2.0.1" entities "^2.0.0" domain-browser@^1.1.1: version "1.2.0" resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda" integrity sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA== domelementtype@1: version "1.3.1" resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f" integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w== domelementtype@^2.0.1: version "2.2.0" resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.2.0.tgz#9a0b6c2782ed6a1c7323d42267183df9bd8b1d57" integrity sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A== domexception@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/domexception/-/domexception-1.0.1.tgz#937442644ca6a31261ef36e3ec677fe805582c90" integrity sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug== dependencies: webidl-conversions "^4.0.2" dompurify@^2.2.6: version "2.3.0" resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.3.0.tgz#07bb39515e491588e5756b1d3e8375b5964814e2" integrity sha512-VV5C6Kr53YVHGOBKO/F86OYX6/iLTw2yVSI721gKetxpHCK/V5TaLEf9ODjRgl1KLSWRMY6cUhAbv/c+IUnwQw== domutils@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.7.0.tgz#56ea341e834e06e6748af7a1cb25da67ea9f8c2a" integrity sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg== dependencies: dom-serializer "0" domelementtype "1" dot-prop@^5.1.0, dot-prop@^5.2.0: version "5.3.0" resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.3.0.tgz#90ccce708cd9cd82cc4dc8c3ddd9abdd55b20e88" integrity sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q== dependencies: is-obj "^2.0.0" dot-prop@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-6.0.1.tgz#fc26b3cf142b9e59b74dbd39ed66ce620c681083" integrity sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA== dependencies: is-obj "^2.0.0" dotnet-deps-parser@5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/dotnet-deps-parser/-/dotnet-deps-parser-5.1.0.tgz#ebb7788c663ba8a6729ef04fc3408581c8ae5047" integrity sha512-/VVFME8IwiYJMC7amuVzHf+CZHiXxYjEjgKpRvvY3lKYFirdqacLwqLlrBl1dYYcUEwmHb/90cssTKInc9pvYg== dependencies: lodash.isempty "^4.4.0" lodash.set "^4.3.2" lodash.uniq "^4.5.0" source-map-support "^0.5.7" tslib "^1.10.0" xml2js "0.4.23" downlevel-dts@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/downlevel-dts/-/downlevel-dts-0.4.0.tgz#43f9f649c8b137373d76b4ee396d5a0227c10ddb" integrity sha512-nh5vM3n2pRhPwZqh0iWo5gpItPAYEGEWw9yd0YpI+lO60B7A3A6iJlxDbt7kKVNbqBXKsptL+jwE/Yg5Go66WQ== dependencies: shelljs "^0.8.3" typescript "^3.8.0-dev.20200111" duplexer3@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2" integrity sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI= duplexer@^0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6" integrity sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg== duplexify@^3.4.2, duplexify@^3.5.0, duplexify@^3.6.0: version "3.7.1" resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.7.1.tgz#2a4df5317f6ccfd91f86d6fd25d8d8a103b88309" integrity sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g== dependencies: end-of-stream "^1.0.0" inherits "^2.0.1" readable-stream "^2.0.0" stream-shift "^1.0.0" ecc-jsbn@~0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" integrity sha1-OoOpBOVDUyh4dMVkt1SThoSamMk= dependencies: jsbn "~0.1.0" safer-buffer "^2.1.0" ecdsa-sig-formatter@1.0.11: version "1.0.11" resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf" integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ== dependencies: safe-buffer "^5.0.1" ee-first@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= electron-to-chromium@^1.3.793: version "1.3.796" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.796.tgz#bd74a4367902c9d432d129f265bf4542cddd9f54" integrity sha512-agwJFgM0FUC1UPPbQ4aII3HamaaJ09fqWGAWYHmzxDWqdmTleCHyyA0kt3fJlTd5M440IaeuBfzXzXzCotnZcQ== elfy@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/elfy/-/elfy-1.0.0.tgz#7a1c86af7d41e0a568cbb4a3fa5b685648d9efcd" integrity sha512-4Kp3AA94jC085IJox+qnvrZ3PudqTi4gQNvIoTZfJJ9IqkRuCoqP60vCVYlIg00c5aYusi5Wjh2bf0cHYt+6gQ== dependencies: endian-reader "^0.3.0" elliptic@^6.5.3: version "6.5.4" resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb" integrity sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ== dependencies: bn.js "^4.11.9" brorand "^1.1.0" hash.js "^1.0.0" hmac-drbg "^1.0.1" inherits "^2.0.4" minimalistic-assert "^1.0.1" minimalistic-crypto-utils "^1.0.1" email-validator@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/email-validator/-/email-validator-2.0.4.tgz#b8dfaa5d0dae28f1b03c95881d904d4e40bfe7ed" integrity sha512-gYCwo7kh5S3IDyZPLZf6hSS0MnZT8QmJFqYvbqlDZSbwdZlY6QZWxJ4i/6UhITOJ4XzyI647Bm2MXKCLqnJ4nQ== emoji-regex@^7.0.1: version "7.0.3" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA== emoji-regex@^8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== emojis-list@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78" integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q== encodeurl@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= encoding@^0.1.12: version "0.1.13" resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.13.tgz#56574afdd791f54a8e9b2785c0582a2d26210fa9" integrity sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A== dependencies: iconv-lite "^0.6.2" end-of-stream@^1.0.0, end-of-stream@^1.1.0, end-of-stream@^1.4.1: version "1.4.4" resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== dependencies: once "^1.4.0" end-of-stream@~1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.1.0.tgz#e9353258baa9108965efc41cb0ef8ade2f3cfb07" integrity sha1-6TUyWLqpEIll78QcsO+K3i88+wc= dependencies: once "~1.3.0" endian-reader@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/endian-reader/-/endian-reader-0.3.0.tgz#84eca436b80aed0d0639c47291338b932efe50a0" integrity sha1-hOykNrgK7Q0GOcRykTOLky7+UKA= engine.io-parser@~4.0.0: version "4.0.2" resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-4.0.2.tgz#e41d0b3fb66f7bf4a3671d2038a154024edb501e" integrity sha512-sHfEQv6nmtJrq6TKuIz5kyEKH/qSdK56H/A+7DnAuUPWosnIZAS2NHNcPLmyjtY3cGS/MqJdZbUjW97JU72iYg== dependencies: base64-arraybuffer "0.1.4" engine.io@~4.1.0: version "4.1.1" resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-4.1.1.tgz#9a8f8a5ac5a5ea316183c489bf7f5b6cf91ace5b" integrity sha512-t2E9wLlssQjGw0nluF6aYyfX8LwYU8Jj0xct+pAhfWfv/YrBn6TSNtEYsgxHIfaMqfrLx07czcMg9bMN6di+3w== dependencies: accepts "~1.3.4" base64id "2.0.0" cookie "~0.4.1" cors "~2.8.5" debug "~4.3.1" engine.io-parser "~4.0.0" ws "~7.4.2" enhanced-resolve@^4.1.1, enhanced-resolve@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.5.0.tgz#2f3cfd84dbe3b487f18f2db2ef1e064a571ca5ec" integrity sha512-Nv9m36S/vxpsI+Hc4/ZGRs0n9mXqSWGGq49zxb/cJfPAQMbUtttJAlNPS4AQzaBdw/pKskw5bMbekT/Y7W/Wlg== dependencies: graceful-fs "^4.1.2" memory-fs "^0.5.0" tapable "^1.0.0" enquirer@^2.3.5, enquirer@^2.3.6: version "2.3.6" resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d" integrity sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg== dependencies: ansi-colors "^4.1.1" ent@~2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/ent/-/ent-2.2.0.tgz#e964219325a21d05f44466a2f686ed6ce5f5dd1d" integrity sha1-6WQhkyWiHQX0RGai9obtbOX13R0= entities@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55" integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A== env-paths@^2.2.0: version "2.2.1" resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2" integrity sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A== envinfo@7.8.1, envinfo@^7.7.4: version "7.8.1" resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.8.1.tgz#06377e3e5f4d379fea7ac592d5ad8927e0c4d475" integrity sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw== err-code@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/err-code/-/err-code-2.0.3.tgz#23c2f3b756ffdfc608d30e27c9a941024807e7f9" integrity sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA== errno@^0.1.3, errno@~0.1.7: version "0.1.8" resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.8.tgz#8bb3e9c7d463be4976ff888f76b4809ebc2e811f" integrity sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A== dependencies: prr "~1.0.1" error-ex@^1.3.1: version "1.3.2" resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== dependencies: is-arrayish "^0.2.1" es-abstract@^1.17.2, es-abstract@^1.18.0-next.2, es-abstract@^1.18.2: version "1.18.5" resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.18.5.tgz#9b10de7d4c206a3581fd5b2124233e04db49ae19" integrity sha512-DDggyJLoS91CkJjgauM5c0yZMjiD1uK3KcaCeAmffGwZ+ODWzOkPN4QwRbsK5DOFf06fywmyLci3ZD8jLGhVYA== dependencies: call-bind "^1.0.2" es-to-primitive "^1.2.1" function-bind "^1.1.1" get-intrinsic "^1.1.1" has "^1.0.3" has-symbols "^1.0.2" internal-slot "^1.0.3" is-callable "^1.2.3" is-negative-zero "^2.0.1" is-regex "^1.1.3" is-string "^1.0.6" object-inspect "^1.11.0" object-keys "^1.1.1" object.assign "^4.1.2" string.prototype.trimend "^1.0.4" string.prototype.trimstart "^1.0.4" unbox-primitive "^1.0.1" es-to-primitive@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== dependencies: is-callable "^1.1.4" is-date-object "^1.0.1" is-symbol "^1.0.2" es5-ext@^0.10.35, es5-ext@^0.10.46, es5-ext@^0.10.50, es5-ext@^0.10.53, es5-ext@~0.10.14, es5-ext@~0.10.2, es5-ext@~0.10.46: version "0.10.53" resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.53.tgz#93c5a3acfdbef275220ad72644ad02ee18368de1" integrity sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q== dependencies: es6-iterator "~2.0.3" es6-symbol "~3.1.3" next-tick "~1.0.0" es6-error@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/es6-error/-/es6-error-4.1.1.tgz#9e3af407459deed47e9a91f9b885a84eb05c561d" integrity sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg== es6-iterator@^2.0.3, es6-iterator@~2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7" integrity sha1-p96IkUGgWpSwhUQDstCg+/qY87c= dependencies: d "1" es5-ext "^0.10.35" es6-symbol "^3.1.1" es6-promise@^4.0.5: version "4.2.8" resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a" integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w== es6-symbol@^3.1.1, es6-symbol@~3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.3.tgz#bad5d3c1bcdac28269f4cb331e431c78ac705d18" integrity sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA== dependencies: d "^1.0.1" ext "^1.1.2" es6-weak-map@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/es6-weak-map/-/es6-weak-map-2.0.3.tgz#b6da1f16cc2cc0d9be43e6bdbfc5e7dfcdf31d53" integrity sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA== dependencies: d "1" es5-ext "^0.10.46" es6-iterator "^2.0.3" es6-symbol "^3.1.1" escalade@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== escape-goat@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/escape-goat/-/escape-goat-2.1.1.tgz#1b2dc77003676c457ec760b2dc68edb648188675" integrity sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q== escape-html@~1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= escape-string-regexp@4.0.0, escape-string-regexp@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= escodegen@^1.11.1: version "1.14.3" resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.14.3.tgz#4e7b81fba61581dc97582ed78cab7f0e8d63f503" integrity sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw== dependencies: esprima "^4.0.1" estraverse "^4.2.0" esutils "^2.0.2" optionator "^0.8.1" optionalDependencies: source-map "~0.6.1" eslint-config-prettier@~6.15.0: version "6.15.0" resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-6.15.0.tgz#7f93f6cb7d45a92f1537a70ecc06366e1ac6fed9" integrity sha512-a1+kOYLR8wMGustcgAjdydMsQ2A/2ipRPwRKUmfYaSxc9ZPcrku080Ctl6zrZzZNs/U82MjSv+qKREkoq3bJaw== dependencies: get-stdin "^6.0.0" eslint-import-resolver-node@0.3.4: version "0.3.4" resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.4.tgz#85ffa81942c25012d8231096ddf679c03042c717" integrity sha512-ogtf+5AB/O+nM6DIeBUNr2fuT7ot9Qg/1harBfBtaP13ekEWFQEEMP94BCB7zaNW3gyY+8SHYF00rnqYwXKWOA== dependencies: debug "^2.6.9" resolve "^1.13.1" eslint-plugin-prettier@~3.1.4: version "3.1.4" resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-3.1.4.tgz#168ab43154e2ea57db992a2cd097c828171f75c2" integrity sha512-jZDa8z76klRqo+TdGDTFJSavwbnWK2ZpqGKNZ+VvweMW516pDUMmQ2koXvxEE4JhzNvTv+radye/bWGBmA6jmg== dependencies: prettier-linter-helpers "^1.0.0" eslint-scope@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.3.tgz#ca03833310f6889a3264781aa82e63eb9cfe7848" integrity sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg== dependencies: esrecurse "^4.1.0" estraverse "^4.1.1" eslint-scope@^5.0.0, eslint-scope@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== dependencies: esrecurse "^4.3.0" estraverse "^4.1.1" eslint-utils@^2.0.0, eslint-utils@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-2.1.0.tgz#d2de5e03424e707dc10c74068ddedae708741b27" integrity sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg== dependencies: eslint-visitor-keys "^1.1.0" eslint-visitor-keys@^1.1.0, eslint-visitor-keys@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e" integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ== eslint-visitor-keys@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303" integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== eslint@~7.14.0: version "7.14.0" resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.14.0.tgz#2d2cac1d28174c510a97b377f122a5507958e344" integrity sha512-5YubdnPXrlrYAFCKybPuHIAH++PINe1pmKNc5wQRB9HSbqIK1ywAnntE3Wwua4giKu0bjligf1gLF6qxMGOYRA== dependencies: "@babel/code-frame" "^7.0.0" "@eslint/eslintrc" "^0.2.1" ajv "^6.10.0" chalk "^4.0.0" cross-spawn "^7.0.2" debug "^4.0.1" doctrine "^3.0.0" enquirer "^2.3.5" eslint-scope "^5.1.1" eslint-utils "^2.1.0" eslint-visitor-keys "^2.0.0" espree "^7.3.0" esquery "^1.2.0" esutils "^2.0.2" file-entry-cache "^5.0.1" functional-red-black-tree "^1.0.1" glob-parent "^5.0.0" globals "^12.1.0" ignore "^4.0.6" import-fresh "^3.0.0" imurmurhash "^0.1.4" is-glob "^4.0.0" js-yaml "^3.13.1" json-stable-stringify-without-jsonify "^1.0.1" levn "^0.4.1" lodash "^4.17.19" minimatch "^3.0.4" natural-compare "^1.4.0" optionator "^0.9.1" progress "^2.0.0" regexpp "^3.1.0" semver "^7.2.1" strip-ansi "^6.0.0" strip-json-comments "^3.1.0" table "^5.2.3" text-table "^0.2.0" v8-compile-cache "^2.0.3" espree@^7.3.0: version "7.3.1" resolved "https://registry.yarnpkg.com/espree/-/espree-7.3.1.tgz#f2df330b752c6f55019f8bd89b7660039c1bbbb6" integrity sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g== dependencies: acorn "^7.4.0" acorn-jsx "^5.3.1" eslint-visitor-keys "^1.3.0" esprima@^4.0.0, esprima@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== esquery@^1.2.0: version "1.4.0" resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.4.0.tgz#2148ffc38b82e8c7057dfed48425b3e61f0f24a5" integrity sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w== dependencies: estraverse "^5.1.0" esrecurse@^4.1.0, esrecurse@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== dependencies: estraverse "^5.2.0" estraverse@^4.1.1, estraverse@^4.2.0: version "4.3.0" resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== estraverse@^5.1.0, estraverse@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.2.0.tgz#307df42547e6cc7324d3cf03c155d5cdb8c53880" integrity sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ== estree-walker@^0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-0.6.1.tgz#53049143f40c6eb918b23671d1fe3219f3a1b362" integrity sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w== estree-walker@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-1.0.1.tgz#31bc5d612c96b704106b477e6dd5d8aa138cb700" integrity sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg== esutils@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== etag@~1.8.1: version "1.8.1" resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= event-emitter@^0.3.5: version "0.3.5" resolved "https://registry.yarnpkg.com/event-emitter/-/event-emitter-0.3.5.tgz#df8c69eef1647923c7157b9ce83840610b02cc39" integrity sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk= dependencies: d "1" es5-ext "~0.10.14" event-loop-spinner@^2.0.0, event-loop-spinner@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/event-loop-spinner/-/event-loop-spinner-2.1.0.tgz#75f501d585105c6d57f174073b39af1b6b3a1567" integrity sha512-RJ10wL8/F9AlfBgRCvYctJIXSb9XkVmSCK3GGUvPD3dJrvTjDeDT0tmhcbEC6I2NEjNM9xD38HQJ4F/f/gb4VQ== dependencies: tslib "^2.1.0" eventemitter3@^4.0.0, eventemitter3@^4.0.4: version "4.0.7" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== events@^3.0.0: version "3.3.0" resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz#7fcbdb198dc71959432efe13842684e0525acb02" integrity sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA== dependencies: md5.js "^1.3.4" safe-buffer "^5.1.1" execa@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/execa/-/execa-1.0.0.tgz#c6236a5bb4df6d6f15e88e7f017798216749ddd8" integrity sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA== dependencies: cross-spawn "^6.0.0" get-stream "^4.0.0" is-stream "^1.1.0" npm-run-path "^2.0.0" p-finally "^1.0.0" signal-exit "^3.0.0" strip-eof "^1.0.0" execa@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/execa/-/execa-4.1.0.tgz#4e5491ad1572f2f17a77d388c6c857135b22847a" integrity sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA== dependencies: cross-spawn "^7.0.0" get-stream "^5.0.0" human-signals "^1.1.1" is-stream "^2.0.0" merge-stream "^2.0.0" npm-run-path "^4.0.0" onetime "^5.1.0" signal-exit "^3.0.2" strip-final-newline "^2.0.0" execa@^5.0.0: version "5.1.1" resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== dependencies: cross-spawn "^7.0.3" get-stream "^6.0.0" human-signals "^2.1.0" is-stream "^2.0.0" merge-stream "^2.0.0" npm-run-path "^4.0.1" onetime "^5.1.2" signal-exit "^3.0.3" strip-final-newline "^2.0.0" expand-brackets@^2.1.4: version "2.1.4" resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622" integrity sha1-t3c14xXOMPa27/D4OwQVGiJEliI= dependencies: debug "^2.3.3" define-property "^0.2.5" extend-shallow "^2.0.1" posix-character-classes "^0.1.0" regex-not "^1.0.0" snapdragon "^0.8.1" to-regex "^3.0.1" expand-tilde@^2.0.0, expand-tilde@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/expand-tilde/-/expand-tilde-2.0.2.tgz#97e801aa052df02454de46b02bf621642cdc8502" integrity sha1-l+gBqgUt8CRU3kawK/YhZCzchQI= dependencies: homedir-polyfill "^1.0.1" express@4.17.1: version "4.17.1" resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134" integrity sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g== dependencies: accepts "~1.3.7" array-flatten "1.1.1" body-parser "1.19.0" content-disposition "0.5.3" content-type "~1.0.4" cookie "0.4.0" cookie-signature "1.0.6" debug "2.6.9" depd "~1.1.2" encodeurl "~1.0.2" escape-html "~1.0.3" etag "~1.8.1" finalhandler "~1.1.2" fresh "0.5.2" merge-descriptors "1.0.1" methods "~1.1.2" on-finished "~2.3.0" parseurl "~1.3.3" path-to-regexp "0.1.7" proxy-addr "~2.0.5" qs "6.7.0" range-parser "~1.2.1" safe-buffer "5.1.2" send "0.17.1" serve-static "1.14.1" setprototypeof "1.1.1" statuses "~1.5.0" type-is "~1.6.18" utils-merge "1.0.1" vary "~1.1.2" ext@^1.1.2: version "1.4.0" resolved "https://registry.yarnpkg.com/ext/-/ext-1.4.0.tgz#89ae7a07158f79d35517882904324077e4379244" integrity sha512-Key5NIsUxdqKg3vIsdw9dSuXpPCQ297y6wBjL30edxwPgt2E44WcWBZey/ZvUc6sERLTxKdyCu4gZFmUbk1Q7A== dependencies: type "^2.0.0" extend-shallow@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" integrity sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8= dependencies: is-extendable "^0.1.0" extend-shallow@^3.0.0, extend-shallow@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-3.0.2.tgz#26a71aaf073b39fb2127172746131c2704028db8" integrity sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg= dependencies: assign-symbols "^1.0.0" is-extendable "^1.0.1" extend@^3.0.0, extend@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== external-editor@^3.0.3: version "3.1.0" resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.1.0.tgz#cb03f740befae03ea4d283caed2741a83f335495" integrity sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew== dependencies: chardet "^0.7.0" iconv-lite "^0.4.24" tmp "^0.0.33" extglob@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543" integrity sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw== dependencies: array-unique "^0.3.2" define-property "^1.0.0" expand-brackets "^2.1.4" extend-shallow "^2.0.1" fragment-cache "^0.2.1" regex-not "^1.0.0" snapdragon "^0.8.1" to-regex "^3.0.1" extract-zip@^1.6.6: version "1.7.0" resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-1.7.0.tgz#556cc3ae9df7f452c493a0cfb51cc30277940927" integrity sha512-xoh5G1W/PB0/27lXgMQyIhP5DSY/LhoCsOyZgb+6iMmRtCwVBo55uKaMoEYrDCKQhWvqEip5ZPKAc6eFNyf/MA== dependencies: concat-stream "^1.6.2" debug "^2.6.9" mkdirp "^0.5.4" yauzl "^2.10.0" extsprintf@1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" integrity sha1-lpGEQOMEGnpBT4xS48V06zw+HgU= extsprintf@^1.2.0: version "1.4.0" resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= fast-deep-equal@^3.1.1: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== fast-diff@^1.1.2: version "1.2.0" resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03" integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w== fast-glob@^3.0.3, fast-glob@^3.1.1, fast-glob@^3.2.2: version "3.2.7" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.7.tgz#fd6cb7a2d7e9aa7a7846111e85a196d6b2f766a1" integrity sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q== dependencies: "@nodelib/fs.stat" "^2.0.2" "@nodelib/fs.walk" "^1.2.3" glob-parent "^5.1.2" merge2 "^1.3.0" micromatch "^4.0.4" fast-json-stable-stringify@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= fast-redact@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/fast-redact/-/fast-redact-3.0.1.tgz#d6015b971e933d03529b01333ba7f22c29961e92" integrity sha512-kYpn4Y/valC9MdrISg47tZOpYBNoTXKgT9GYXFpHN/jYFs+lFkPoisY+LcBODdKVMY96ATzvzsWv+ES/4Kmufw== fast-safe-stringify@^2.0.8: version "2.0.8" resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.0.8.tgz#dc2af48c46cf712b683e849b2bbd446b32de936f" integrity sha512-lXatBjf3WPjmWD6DpIZxkeSsCOwqI0maYMpgDlx8g4U2qi4lbjA9oH/HD2a87G+KfsUmo5WbJFmqBZlPxtptag== fastq@^1.6.0: version "1.11.1" resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.11.1.tgz#5d8175aae17db61947f8b162cfc7f63264d22807" integrity sha512-HOnr8Mc60eNYl1gzwp6r5RoUyAn5/glBolUzP/Ez6IFVPMPirxn/9phgL6zhOtaTy7ISwPvQ+wT+hfcRZh/bzw== dependencies: reusify "^1.0.4" fd-slicer@~1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e" integrity sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4= dependencies: pend "~1.2.0" figgy-pudding@^3.5.1: version "3.5.2" resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.2.tgz#b4eee8148abb01dcf1d1ac34367d59e12fa61d6e" integrity sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw== figures@^3.0.0: version "3.2.0" resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af" integrity sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg== dependencies: escape-string-regexp "^1.0.5" file-entry-cache@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-5.0.1.tgz#ca0f6efa6dd3d561333fb14515065c2fafdf439c" integrity sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g== dependencies: flat-cache "^2.0.1" file-loader@^5.0.2: version "5.1.0" resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-5.1.0.tgz#cb56c070efc0e40666424309bd0d9e45ac6f2bb8" integrity sha512-u/VkLGskw3Ue59nyOwUwXI/6nuBCo7KBkniB/l7ICwr/7cPNGsL1WCXUp3GB0qgOOKU1TiP49bv4DZF/LJqprg== dependencies: loader-utils "^1.4.0" schema-utils "^2.5.0" file-uri-to-path@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== fill-range@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7" integrity sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc= dependencies: extend-shallow "^2.0.1" is-number "^3.0.0" repeat-string "^1.6.1" to-regex-range "^2.1.0" fill-range@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== dependencies: to-regex-range "^5.0.1" filter-obj@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/filter-obj/-/filter-obj-1.1.0.tgz#9b311112bc6c6127a16e016c6c5d7f19e0805c5b" integrity sha1-mzERErxsYSehbgFsbF1/GeCAXFs= finalhandler@1.1.2, finalhandler@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d" integrity sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA== dependencies: debug "2.6.9" encodeurl "~1.0.2" escape-html "~1.0.3" on-finished "~2.3.0" parseurl "~1.3.3" statuses "~1.5.0" unpipe "~1.0.0" find-cache-dir@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-2.1.0.tgz#8d0f94cd13fe43c6c7c261a0d86115ca918c05f7" integrity sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ== dependencies: commondir "^1.0.1" make-dir "^2.0.0" pkg-dir "^3.0.0" find-up@5.0.0, find-up@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== dependencies: locate-path "^6.0.0" path-exists "^4.0.0" find-up@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" integrity sha1-RdG35QbHF93UgndaK3eSCjwMV6c= dependencies: locate-path "^2.0.0" find-up@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg== dependencies: locate-path "^3.0.0" find-up@^4.0.0, find-up@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== dependencies: locate-path "^5.0.0" path-exists "^4.0.0" find-versions@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/find-versions/-/find-versions-4.0.0.tgz#3c57e573bf97769b8cb8df16934b627915da4965" integrity sha512-wgpWy002tA+wgmO27buH/9KzyEOQnKsG/R0yrcjPT9BOFm0zRBVQbZ95nRGXWMywS8YR5knRbpohio0bcJABxQ== dependencies: semver-regex "^3.1.2" findup-sync@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-3.0.0.tgz#17b108f9ee512dfb7a5c7f3c8b27ea9e1a9c08d1" integrity sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg== dependencies: detect-file "^1.0.0" is-glob "^4.0.0" micromatch "^3.0.4" resolve-dir "^1.0.1" flat-cache@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-2.0.1.tgz#5d296d6f04bda44a4630a301413bdbc2ec085ec0" integrity sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA== dependencies: flatted "^2.0.0" rimraf "2.6.3" write "1.0.3" flat@^5.0.2: version "5.0.2" resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241" integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== flatstr@^1.0.12: version "1.0.12" resolved "https://registry.yarnpkg.com/flatstr/-/flatstr-1.0.12.tgz#c2ba6a08173edbb6c9640e3055b95e287ceb5931" integrity sha512-4zPxDyhCyiN2wIAtSLI6gc82/EjqZc1onI4Mz/l0pWrAlsSfYH/2ZIcU+e3oA2wDwbzIWNKwa23F8rh6+DRWkw== flatted@^2.0.0, flatted@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.2.tgz#4575b21e2bcee7434aa9be662f4b7b5f9c2b5138" integrity sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA== flush-write-stream@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.1.1.tgz#8dd7d873a1babc207d94ead0c2e0e44276ebf2e8" integrity sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w== dependencies: inherits "^2.0.3" readable-stream "^2.3.6" follow-redirects@^1.0.0: version "1.14.1" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.1.tgz#d9114ded0a1cfdd334e164e6662ad02bfd91ff43" integrity sha512-HWqDgT7ZEkqRzBvc2s64vSZ/hfOceEol3ac/7tKwzuvEyWx3/4UegXh5oBOIotkGsObyk3xznnSRVADBgWSQVg== for-in@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" integrity sha1-gQaNKVqBQuwKxybG4iAMMPttXoA= forever-agent@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= form-data@~2.3.2: version "2.3.3" resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ== dependencies: asynckit "^0.4.0" combined-stream "^1.0.6" mime-types "^2.1.12" forwarded@0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== fragment-cache@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" integrity sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk= dependencies: map-cache "^0.2.2" fresh@0.5.2: version "0.5.2" resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= from2@^2.1.0: version "2.3.0" resolved "https://registry.yarnpkg.com/from2/-/from2-2.3.0.tgz#8bfb5502bde4a4d36cfdeea007fcca21d7e382af" integrity sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8= dependencies: inherits "^2.0.1" readable-stream "^2.0.0" fs-constants@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== fs-extra@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g== dependencies: graceful-fs "^4.2.0" jsonfile "^4.0.0" universalify "^0.1.0" fs-extra@^9.0.1, fs-extra@^9.1.0: version "9.1.0" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d" integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ== dependencies: at-least-node "^1.0.0" graceful-fs "^4.2.0" jsonfile "^6.0.1" universalify "^2.0.0" fs-extra@~7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9" integrity sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw== dependencies: graceful-fs "^4.1.2" jsonfile "^4.0.0" universalify "^0.1.0" fs-minipass@^1.2.7: version "1.2.7" resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.7.tgz#ccff8570841e7fe4265693da88936c55aed7f7c7" integrity sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA== dependencies: minipass "^2.6.0" fs-minipass@^2.0.0, fs-minipass@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg== dependencies: minipass "^3.0.0" fs-write-stream-atomic@^1.0.8: version "1.0.10" resolved "https://registry.yarnpkg.com/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz#b47df53493ef911df75731e70a9ded0189db40c9" integrity sha1-tH31NJPvkR33VzHnCp3tAYnbQMk= dependencies: graceful-fs "^4.1.2" iferr "^0.1.5" imurmurhash "^0.1.4" readable-stream "1 || 2" fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= fsevents@^1.2.7: version "1.2.13" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.13.tgz#f325cb0455592428bcf11b383370ef70e3bfcc38" integrity sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw== dependencies: bindings "^1.5.0" nan "^2.12.1" fsevents@~2.3.2: version "2.3.2" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== function-bind@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== functional-red-black-tree@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= gauge@~2.7.3: version "2.7.4" resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" integrity sha1-LANAXHU4w51+s3sxcCLjJfsBi/c= dependencies: aproba "^1.0.3" console-control-strings "^1.0.0" has-unicode "^2.0.0" object-assign "^4.1.0" signal-exit "^3.0.0" string-width "^1.0.1" strip-ansi "^3.0.1" wide-align "^1.1.0" generic-names@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/generic-names/-/generic-names-2.0.1.tgz#f8a378ead2ccaa7a34f0317b05554832ae41b872" integrity sha512-kPCHWa1m9wGG/OwQpeweTwM/PYiQLrUIxXbt/P4Nic3LbGjCP0YwrALHW1uNLKZ0LIMg+RF+XRlj2ekT9ZlZAQ== dependencies: loader-utils "^1.1.0" get-caller-file@^2.0.1, get-caller-file@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== get-func-name@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41" integrity sha1-6td0q+5y4gQJQzoGY2YCPdaIekE= get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6" integrity sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q== dependencies: function-bind "^1.1.1" has "^1.0.3" has-symbols "^1.0.1" get-own-enumerable-property-symbols@^3.0.0: version "3.0.2" resolved "https://registry.yarnpkg.com/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz#b5fde77f22cbe35f390b4e089922c50bce6ef664" integrity sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g== get-pkg-repo@^4.0.0: version "4.1.2" resolved "https://registry.yarnpkg.com/get-pkg-repo/-/get-pkg-repo-4.1.2.tgz#c4ffd60015cf091be666a0212753fc158f01a4c0" integrity sha512-/FjamZL9cBYllEbReZkxF2IMh80d8TJoC4e3bmLNif8ibHw95aj0N/tzqK0kZz9eU/3w3dL6lF4fnnX/sDdW3A== dependencies: "@hutson/parse-repository-url" "^3.0.0" hosted-git-info "^4.0.0" meow "^7.0.0" through2 "^2.0.0" get-port@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/get-port/-/get-port-5.1.1.tgz#0469ed07563479de6efb986baf053dcd7d4e3193" integrity sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ== get-stdin@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-6.0.0.tgz#9e09bf712b360ab9225e812048f71fde9c89657b" integrity sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g== get-stream@^4.0.0, get-stream@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w== dependencies: pump "^3.0.0" get-stream@^5.0.0, get-stream@^5.1.0: version "5.2.0" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== dependencies: pump "^3.0.0" get-stream@^6.0.0: version "6.0.1" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== get-value@^2.0.3, get-value@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" integrity sha1-3BXKHGcjh8p2vTesCjlbogQqLCg= getpass@^0.1.1: version "0.1.7" resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" integrity sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo= dependencies: assert-plus "^1.0.0" git-hooks-list@1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/git-hooks-list/-/git-hooks-list-1.0.3.tgz#be5baaf78203ce342f2f844a9d2b03dba1b45156" integrity sha512-Y7wLWcrLUXwk2noSka166byGCvhMtDRpgHdzCno1UQv/n/Hegp++a2xBWJL1lJarnKD3SWaljD+0z1ztqxuKyQ== git-raw-commits@^2.0.8: version "2.0.10" resolved "https://registry.yarnpkg.com/git-raw-commits/-/git-raw-commits-2.0.10.tgz#e2255ed9563b1c9c3ea6bd05806410290297bbc1" integrity sha512-sHhX5lsbG9SOO6yXdlwgEMQ/ljIn7qMpAbJZCGfXX2fq5T8M5SrDnpYk9/4HswTildcIqatsWa91vty6VhWSaQ== dependencies: dargs "^7.0.0" lodash "^4.17.15" meow "^8.0.0" split2 "^3.0.0" through2 "^4.0.0" git-remote-origin-url@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/git-remote-origin-url/-/git-remote-origin-url-2.0.0.tgz#5282659dae2107145a11126112ad3216ec5fa65f" integrity sha1-UoJlna4hBxRaERJhEq0yFuxfpl8= dependencies: gitconfiglocal "^1.0.0" pify "^2.3.0" git-semver-tags@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/git-semver-tags/-/git-semver-tags-4.1.1.tgz#63191bcd809b0ec3e151ba4751c16c444e5b5780" integrity sha512-OWyMt5zBe7xFs8vglMmhM9lRQzCWL3WjHtxNNfJTMngGym7pC1kh8sP6jevfydJ6LP3ZvGxfb6ABYgPUM0mtsA== dependencies: meow "^8.0.0" semver "^6.0.0" git-up@^4.0.0: version "4.0.5" resolved "https://registry.yarnpkg.com/git-up/-/git-up-4.0.5.tgz#e7bb70981a37ea2fb8fe049669800a1f9a01d759" integrity sha512-YUvVDg/vX3d0syBsk/CKUTib0srcQME0JyHkL5BaYdwLsiCslPWmDSi8PUMo9pXYjrryMcmsCoCgsTpSCJEQaA== dependencies: is-ssh "^1.3.0" parse-url "^6.0.0" git-url-parse@^11.4.4: version "11.5.0" resolved "https://registry.yarnpkg.com/git-url-parse/-/git-url-parse-11.5.0.tgz#acaaf65239cb1536185b19165a24bbc754b3f764" integrity sha512-TZYSMDeM37r71Lqg1mbnMlOqlHd7BSij9qN7XwTkRqSAYFMihGLGhfHwgqQob3GUhEneKnV4nskN9rbQw2KGxA== dependencies: git-up "^4.0.0" gitconfiglocal@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/gitconfiglocal/-/gitconfiglocal-1.0.0.tgz#41d045f3851a5ea88f03f24ca1c6178114464b9b" integrity sha1-QdBF84UaXqiPA/JMocYXgRRGS5s= dependencies: ini "^1.3.2" glob-parent@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae" integrity sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4= dependencies: is-glob "^3.1.0" path-dirname "^1.0.0" glob-parent@^5.0.0, glob-parent@^5.1.1, glob-parent@^5.1.2, glob-parent@~5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== dependencies: is-glob "^4.0.1" glob@7.1.7, glob@^7.0.0, glob@^7.1.1, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@^7.1.7, glob@~7.1.6: version "7.1.7" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90" integrity sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ== dependencies: fs.realpath "^1.0.0" inflight "^1.0.4" inherits "2" minimatch "^3.0.4" once "^1.3.0" path-is-absolute "^1.0.0" glob@^6.0.1: version "6.0.4" resolved "https://registry.yarnpkg.com/glob/-/glob-6.0.4.tgz#0f08860f6a155127b2fadd4f9ce24b1aab6e4d22" integrity sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI= dependencies: inflight "^1.0.4" inherits "2" minimatch "2 || 3" once "^1.3.0" path-is-absolute "^1.0.0" global-agent@^2.1.12: version "2.2.0" resolved "https://registry.yarnpkg.com/global-agent/-/global-agent-2.2.0.tgz#566331b0646e6bf79429a16877685c4a1fbf76dc" integrity sha512-+20KpaW6DDLqhG7JDiJpD1JvNvb8ts+TNl7BPOYcURqCrXqnN1Vf+XVOrkKJAFPqfX+oEhsdzOj1hLWkBTdNJg== dependencies: boolean "^3.0.1" core-js "^3.6.5" es6-error "^4.1.1" matcher "^3.0.0" roarr "^2.15.3" semver "^7.3.2" serialize-error "^7.0.1" global-dirs@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-3.0.0.tgz#70a76fe84ea315ab37b1f5576cbde7d48ef72686" integrity sha512-v8ho2DS5RiCjftj1nD9NmnfaOzTdud7RRnVd9kFNOjqZbISlx5DQ+OrTkywgd0dIt7oFCvKetZSHoHcP3sDdiA== dependencies: ini "2.0.0" global-modules@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-1.0.0.tgz#6d770f0eb523ac78164d72b5e71a8877265cc3ea" integrity sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg== dependencies: global-prefix "^1.0.1" is-windows "^1.0.1" resolve-dir "^1.0.0" global-modules@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-2.0.0.tgz#997605ad2345f27f51539bea26574421215c7780" integrity sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A== dependencies: global-prefix "^3.0.0" global-prefix@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-1.0.2.tgz#dbf743c6c14992593c655568cb66ed32c0122ebe" integrity sha1-2/dDxsFJklk8ZVVoy2btMsASLr4= dependencies: expand-tilde "^2.0.2" homedir-polyfill "^1.0.1" ini "^1.3.4" is-windows "^1.0.1" which "^1.2.14" global-prefix@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-3.0.0.tgz#fc85f73064df69f50421f47f883fe5b913ba9b97" integrity sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg== dependencies: ini "^1.3.5" kind-of "^6.0.2" which "^1.3.1" globals@^12.1.0: version "12.4.0" resolved "https://registry.yarnpkg.com/globals/-/globals-12.4.0.tgz#a18813576a41b00a24a97e7f815918c2e19925f8" integrity sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg== dependencies: type-fest "^0.8.1" globalthis@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.2.tgz#2a235d34f4d8036219f7e34929b5de9e18166b8b" integrity sha512-ZQnSFO1la8P7auIOQECnm0sSuoMeaSq0EEdXMBFF2QJO4uNcwbyhSgG3MruWNbFTqCLmxVwGOl7LZ9kASvHdeQ== dependencies: define-properties "^1.1.3" globby@10.0.0: version "10.0.0" resolved "https://registry.yarnpkg.com/globby/-/globby-10.0.0.tgz#abfcd0630037ae174a88590132c2f6804e291072" integrity sha512-3LifW9M4joGZasyYPz2A1U74zbC/45fvpXUvO/9KbSa+VV0aGZarWkfdgKyR9sExNP0t0x0ss/UMJpNpcaTspw== dependencies: "@types/glob" "^7.1.1" array-union "^2.1.0" dir-glob "^3.0.1" fast-glob "^3.0.3" glob "^7.1.3" ignore "^5.1.1" merge2 "^1.2.3" slash "^3.0.0" globby@^11.0.1, globby@^11.0.2: version "11.0.4" resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.4.tgz#2cbaff77c2f2a62e71e9b2813a67b97a3a3001a5" integrity sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg== dependencies: array-union "^2.1.0" dir-glob "^3.0.1" fast-glob "^3.1.1" ignore "^5.1.4" merge2 "^1.3.0" slash "^3.0.0" got@11.8.2, got@^11.7.0: version "11.8.2" resolved "https://registry.yarnpkg.com/got/-/got-11.8.2.tgz#7abb3959ea28c31f3576f1576c1effce23f33599" integrity sha512-D0QywKgIe30ODs+fm8wMZiAcZjypcCodPNuMz5H9Mny7RJ+IjJ10BdmGW7OM7fHXP+O7r6ZwapQ/YQmMSvB0UQ== dependencies: "@sindresorhus/is" "^4.0.0" "@szmarczak/http-timer" "^4.0.5" "@types/cacheable-request" "^6.0.1" "@types/responselike" "^1.0.0" cacheable-lookup "^5.0.3" cacheable-request "^7.0.1" decompress-response "^6.0.0" http2-wrapper "^1.0.0-beta.5.2" lowercase-keys "^2.0.0" p-cancelable "^2.0.0" responselike "^2.0.0" got@^9.6.0: version "9.6.0" resolved "https://registry.yarnpkg.com/got/-/got-9.6.0.tgz#edf45e7d67f99545705de1f7bbeeeb121765ed85" integrity sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q== dependencies: "@sindresorhus/is" "^0.14.0" "@szmarczak/http-timer" "^1.1.2" cacheable-request "^6.0.0" decompress-response "^3.3.0" duplexer3 "^0.1.4" get-stream "^4.1.0" lowercase-keys "^1.0.1" mimic-response "^1.0.1" p-cancelable "^1.0.0" to-readable-stream "^1.0.0" url-parse-lax "^3.0.0" graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.3, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.2, graceful-fs@^4.2.3, graceful-fs@^4.2.4, graceful-fs@^4.2.6: version "4.2.6" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee" integrity sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ== grapheme-splitter@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz#9cf3a665c6247479896834af35cf1dbb4400767e" integrity sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ== growl@1.10.5: version "1.10.5" resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e" integrity sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA== gunzip-maybe@^1.4.2: version "1.4.2" resolved "https://registry.yarnpkg.com/gunzip-maybe/-/gunzip-maybe-1.4.2.tgz#b913564ae3be0eda6f3de36464837a9cd94b98ac" integrity sha512-4haO1M4mLO91PW57BMsDFf75UmwoRX0GkdD+Faw+Lr+r/OZrOCS0pIBwOL1xCKQqnQzbNFGgK2V2CpBUPeFNTw== dependencies: browserify-zlib "^0.1.4" is-deflate "^1.0.0" is-gzip "^1.0.0" peek-stream "^1.1.0" pumpify "^1.3.3" through2 "^2.0.3" handlebars@4.7.7, handlebars@^4.7.0, handlebars@^4.7.6: version "4.7.7" resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.7.tgz#9ce33416aad02dbd6c8fafa8240d5d98004945a1" integrity sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA== dependencies: minimist "^1.2.5" neo-async "^2.6.0" source-map "^0.6.1" wordwrap "^1.0.0" optionalDependencies: uglify-js "^3.1.4" har-schema@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI= har-validator@~5.1.0, har-validator@~5.1.3: version "5.1.5" resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.5.tgz#1f0803b9f8cb20c0fa13822df1ecddb36bde1efd" integrity sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w== dependencies: ajv "^6.12.3" har-schema "^2.0.0" hard-rejection@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/hard-rejection/-/hard-rejection-2.1.0.tgz#1c6eda5c1685c63942766d79bb40ae773cecd883" integrity sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA== has-bigints@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.1.tgz#64fe6acb020673e3b78db035a5af69aa9d07b113" integrity sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA== has-flag@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= has-flag@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== has-symbols@^1.0.1, has-symbols@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.2.tgz#165d3070c00309752a1236a479331e3ac56f1423" integrity sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw== has-unicode@^2.0.0, has-unicode@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk= has-value@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" integrity sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8= dependencies: get-value "^2.0.3" has-values "^0.1.4" isobject "^2.0.0" has-value@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/has-value/-/has-value-1.0.0.tgz#18b281da585b1c5c51def24c930ed29a0be6b177" integrity sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc= dependencies: get-value "^2.0.6" has-values "^1.0.0" isobject "^3.0.0" has-values@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/has-values/-/has-values-0.1.4.tgz#6d61de95d91dfca9b9a02089ad384bff8f62b771" integrity sha1-bWHeldkd/Km5oCCJrThL/49it3E= has-values@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/has-values/-/has-values-1.0.0.tgz#95b0b63fec2146619a6fe57fe75628d5a39efe4f" integrity sha1-lbC2P+whRmGab+V/51Yo1aOe/k8= dependencies: is-number "^3.0.0" kind-of "^4.0.0" has-yarn@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/has-yarn/-/has-yarn-2.1.0.tgz#137e11354a7b5bf11aa5cb649cf0c6f3ff2b2e77" integrity sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw== has@^1.0.0, has@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== dependencies: function-bind "^1.1.1" hash-base@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.1.0.tgz#55c381d9e06e1d2997a883b4a3fddfe7f0d3af33" integrity sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA== dependencies: inherits "^2.0.4" readable-stream "^3.6.0" safe-buffer "^5.2.0" hash.js@^1.0.0, hash.js@^1.0.3: version "1.1.7" resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42" integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA== dependencies: inherits "^2.0.3" minimalistic-assert "^1.0.1" he@1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== hex-color-regex@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e" integrity sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ== highlight.js@^9.17.1: version "9.18.5" resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.18.5.tgz#d18a359867f378c138d6819edfc2a8acd5f29825" integrity sha512-a5bFyofd/BHCX52/8i8uJkjr9DYwXIPnM/plwI6W7ezItLGqzt7X2G2nXuYSfsIJdkwwj/g9DG1LkcGJI/dDoA== hmac-drbg@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" integrity sha1-0nRXAQJabHdabFRXk+1QL8DGSaE= dependencies: hash.js "^1.0.3" minimalistic-assert "^1.0.0" minimalistic-crypto-utils "^1.0.1" homedir-polyfill@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz#743298cef4e5af3e194161fbadcc2151d3a058e8" integrity sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA== dependencies: parse-passwd "^1.0.0" hosted-git-info@^2.1.4, hosted-git-info@^2.7.1: version "2.8.9" resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9" integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw== hosted-git-info@^3.0.4, hosted-git-info@^3.0.7: version "3.0.8" resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-3.0.8.tgz#6e35d4cc87af2c5f816e4cb9ce350ba87a3f370d" integrity sha512-aXpmwoOhRBrw6X3j0h5RloK4x1OzsxMPyxqIHyNfSe2pypkVTZFpEiRoSipPEPlMrh0HW/XsjkJ5WgnCirpNUw== dependencies: lru-cache "^6.0.0" hosted-git-info@^4.0.0, hosted-git-info@^4.0.1, hosted-git-info@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-4.0.2.tgz#5e425507eede4fea846b7262f0838456c4209961" integrity sha512-c9OGXbZ3guC/xOlCg1Ci/VgWlwsqDv1yMQL1CWqXDL0hDjXuNcq0zuR4xqPSuasI3kqFDhqSyTjREz5gzq0fXg== dependencies: lru-cache "^6.0.0" hsl-regex@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/hsl-regex/-/hsl-regex-1.0.0.tgz#d49330c789ed819e276a4c0d272dffa30b18fe6e" integrity sha1-1JMwx4ntgZ4nakwNJy3/owsY/m4= hsla-regex@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/hsla-regex/-/hsla-regex-1.0.0.tgz#c1ce7a3168c8c6614033a4b5f7877f3b225f9c38" integrity sha1-wc56MWjIxmFAM6S194d/OyJfnDg= html-encoding-sniffer@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz#e70d84b94da53aa375e11fe3a351be6642ca46f8" integrity sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw== dependencies: whatwg-encoding "^1.0.1" http-cache-semantics@^4.0.0, http-cache-semantics@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390" integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ== http-errors@1.7.2: version "1.7.2" resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f" integrity sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg== dependencies: depd "~1.1.2" inherits "2.0.3" setprototypeof "1.1.1" statuses ">= 1.5.0 < 2" toidentifier "1.0.0" http-errors@1.8.0: version "1.8.0" resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.8.0.tgz#75d1bbe497e1044f51e4ee9e704a62f28d336507" integrity sha512-4I8r0C5JDhT5VkvI47QktDW75rNlGVsUf/8hzjCC/wkWI/jdTRmBb9aI7erSG82r1bjKY3F6k28WnsVxB1C73A== dependencies: depd "~1.1.2" inherits "2.0.4" setprototypeof "1.2.0" statuses ">= 1.5.0 < 2" toidentifier "1.0.0" http-errors@~1.7.2: version "1.7.3" resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06" integrity sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw== dependencies: depd "~1.1.2" inherits "2.0.4" setprototypeof "1.1.1" statuses ">= 1.5.0 < 2" toidentifier "1.0.0" http-proxy-agent@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz#8a8c8ef7f5932ccf953c296ca8291b95aa74aa3a" integrity sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg== dependencies: "@tootallnate/once" "1" agent-base "6" debug "4" http-proxy@^1.18.1: version "1.18.1" resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.18.1.tgz#401541f0534884bbf95260334e72f88ee3976549" integrity sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ== dependencies: eventemitter3 "^4.0.0" follow-redirects "^1.0.0" requires-port "^1.0.0" http-signature@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" integrity sha1-muzZJRFHcvPZW2WmCruPfBj7rOE= dependencies: assert-plus "^1.0.0" jsprim "^1.2.2" sshpk "^1.7.0" http-status-codes@1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/http-status-codes/-/http-status-codes-1.4.0.tgz#6e4c15d16ff3a9e2df03b89f3a55e1aae05fb477" integrity sha512-JrT3ua+WgH8zBD3HEJYbeEgnuQaAnUeRRko/YojPAJjGmIfGD3KPU/asLdsLwKjfxOmQe5nXMQ0pt/7MyapVbQ== http2-wrapper@^1.0.0-beta.5.2: version "1.0.3" resolved "https://registry.yarnpkg.com/http2-wrapper/-/http2-wrapper-1.0.3.tgz#b8f55e0c1f25d4ebd08b3b0c2c079f9590800b3d" integrity sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg== dependencies: quick-lru "^5.1.1" resolve-alpn "^1.0.0" https-browserify@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM= https-proxy-agent@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-4.0.0.tgz#702b71fb5520a132a66de1f67541d9e62154d82b" integrity sha512-zoDhWrkR3of1l9QAL8/scJZyLu8j/gBkcwcaQOZh7Gyh/+uJQzGVETdgT30akuwkpL8HTRfssqI3BZuV18teDg== dependencies: agent-base "5" debug "4" https-proxy-agent@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz#e2a90542abb68a762e0a0850f6c9edadfd8506b2" integrity sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA== dependencies: agent-base "6" debug "4" human-signals@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3" integrity sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw== human-signals@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== humanize-ms@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/humanize-ms/-/humanize-ms-1.2.1.tgz#c46e3159a293f6b896da29316d8b6fe8bb79bbed" integrity sha1-xG4xWaKT9riW2ikxbYtv6Lt5u+0= dependencies: ms "^2.0.0" husky@^4.2.5: version "4.3.8" resolved "https://registry.yarnpkg.com/husky/-/husky-4.3.8.tgz#31144060be963fd6850e5cc8f019a1dfe194296d" integrity sha512-LCqqsB0PzJQ/AlCgfrfzRe3e3+NvmefAdKQhRYpxS4u6clblBoDdzzvHi8fmxKRzvMxPY/1WZWzomPZww0Anow== dependencies: chalk "^4.0.0" ci-info "^2.0.0" compare-versions "^3.6.0" cosmiconfig "^7.0.0" find-versions "^4.0.0" opencollective-postinstall "^2.0.2" pkg-dir "^5.0.0" please-upgrade-node "^3.2.0" slash "^3.0.0" which-pm-runs "^1.0.0" iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@^0.4.4: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== dependencies: safer-buffer ">= 2.1.2 < 3" iconv-lite@^0.6.2: version "0.6.3" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== dependencies: safer-buffer ">= 2.1.2 < 3.0.0" icss-replace-symbols@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz#06ea6f83679a7749e386cfe1fe812ae5db223ded" integrity sha1-Bupvg2ead0njhs/h/oEq5dsiPe0= icss-utils@^4.0.0, icss-utils@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-4.1.1.tgz#21170b53789ee27447c2f47dd683081403f9a467" integrity sha512-4aFq7wvWyMHKgxsH8QQtGpvbASCf+eM3wPRLI6R+MgAnTCZ6STYsRvttLvRWK0Nfif5piF394St3HeJDaljGPA== dependencies: postcss "^7.0.14" icss-utils@^5.0.0: version "5.1.0" resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-5.1.0.tgz#c6be6858abd013d768e98366ae47e25d5887b1ae" integrity sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA== ieee754@^1.1.13, ieee754@^1.1.4: version "1.2.1" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== iferr@^0.1.5: version "0.1.5" resolved "https://registry.yarnpkg.com/iferr/-/iferr-0.1.5.tgz#c60eed69e6d8fdb6b3104a1fcbca1c192dc5b501" integrity sha1-xg7taebY/bazEEofy8ocGS3FtQE= ignore-walk@^3.0.3: version "3.0.4" resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.4.tgz#c9a09f69b7c7b479a5d74ac1a3c0d4236d2a6335" integrity sha512-PY6Ii8o1jMRA1z4F2hRkH/xN59ox43DavKvD3oDpfurRlOJyAHpifIwpbdv1n4jt4ov0jSpw3kQ4GhJnpBL6WQ== dependencies: minimatch "^3.0.4" ignore@^4.0.6: version "4.0.6" resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== ignore@^5.1.1, ignore@^5.1.4, ignore@^5.1.8: version "5.1.8" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57" integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw== immediate@~3.0.5: version "3.0.6" resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b" integrity sha1-nbHb0Pr43m++D13V5Wu2BigN5ps= import-cwd@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/import-cwd/-/import-cwd-3.0.0.tgz#20845547718015126ea9b3676b7592fb8bd4cf92" integrity sha512-4pnzH16plW+hgvRECbDWpQl3cqtvSofHWh44met7ESfZ8UZOWWddm8hEyDTqREJ9RbYHY8gi8DqmaelApoOGMg== dependencies: import-from "^3.0.0" import-fresh@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-2.0.0.tgz#d81355c15612d386c61f9ddd3922d4304822a546" integrity sha1-2BNVwVYS04bGH53dOSLUMEgipUY= dependencies: caller-path "^2.0.0" resolve-from "^3.0.0" import-fresh@^3.0.0, import-fresh@^3.2.1: version "3.3.0" resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== dependencies: parent-module "^1.0.0" resolve-from "^4.0.0" import-from@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/import-from/-/import-from-3.0.0.tgz#055cfec38cd5a27d8057ca51376d7d3bf0891966" integrity sha512-CiuXOFFSzkU5x/CR0+z7T91Iht4CXgfCxVOFRhh2Zyhg5wOpWvvDLQUsWl+gcN+QscYBjez8hDCt85O7RLDttQ== dependencies: resolve-from "^5.0.0" import-lazy@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-2.1.0.tgz#05698e3d45c88e8d7e9d92cb0584e77f096f3e43" integrity sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM= import-lazy@~4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-4.0.0.tgz#e8eb627483a0a43da3c03f3e35548be5cb0cc153" integrity sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw== import-local@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/import-local/-/import-local-2.0.0.tgz#55070be38a5993cf18ef6db7e961f5bee5c5a09d" integrity sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ== dependencies: pkg-dir "^3.0.0" resolve-cwd "^2.0.0" import-local@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.0.2.tgz#a8cfd0431d1de4a2199703d003e3e62364fa6db6" integrity sha512-vjL3+w0oulAVZ0hBHnxa/Nm5TAurf9YLQJDhqRZyqb+VKGOB6LU8t9H1Nr5CIo16vh9XfJTOoHwU0B71S557gA== dependencies: pkg-dir "^4.2.0" resolve-cwd "^3.0.0" imurmurhash@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= indent-string@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== indexes-of@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607" integrity sha1-8w9xbI4r00bHtn0985FVZqfAVgc= infer-owner@^1.0.3, infer-owner@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/infer-owner/-/infer-owner-1.0.4.tgz#c4cefcaa8e51051c2a40ba2ce8a3d27295af9467" integrity sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A== inflight@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= dependencies: once "^1.3.0" wrappy "1" inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1, inherits@~2.0.3: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== inherits@2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1" integrity sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE= inherits@2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= ini@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ini/-/ini-2.0.0.tgz#e5fd556ecdd5726be978fa1001862eacb0a94bc5" integrity sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA== ini@^1.3.2, ini@^1.3.4, ini@^1.3.5, ini@~1.3.0: version "1.3.8" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== init-package-json@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/init-package-json/-/init-package-json-2.0.3.tgz#c8ae4f2a4ad353bcbc089e5ffe98a8f1a314e8fd" integrity sha512-tk/gAgbMMxR6fn1MgMaM1HpU1ryAmBWWitnxG5OhuNXeX0cbpbgV5jA4AIpQJVNoyOfOevTtO6WX+rPs+EFqaQ== dependencies: glob "^7.1.1" npm-package-arg "^8.1.2" promzard "^0.3.0" read "~1.0.1" read-package-json "^3.0.1" semver "^7.3.5" validate-npm-package-license "^3.0.4" validate-npm-package-name "^3.0.0" inquirer@^7.0.0, inquirer@^7.3.3: version "7.3.3" resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-7.3.3.tgz#04d176b2af04afc157a83fd7c100e98ee0aad003" integrity sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA== dependencies: ansi-escapes "^4.2.1" chalk "^4.1.0" cli-cursor "^3.1.0" cli-width "^3.0.0" external-editor "^3.0.3" figures "^3.0.0" lodash "^4.17.19" mute-stream "0.0.8" run-async "^2.4.0" rxjs "^6.6.0" string-width "^4.1.0" strip-ansi "^6.0.0" through "^2.3.6" internal-slot@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.3.tgz#7347e307deeea2faac2ac6205d4bc7d34967f59c" integrity sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA== dependencies: get-intrinsic "^1.1.0" has "^1.0.3" side-channel "^1.0.4" interpret@^1.0.0, interpret@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e" integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA== ip-regex@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9" integrity sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk= ip@^1.1.5: version "1.1.5" resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a" integrity sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo= ipaddr.js@1.9.1: version "1.9.1" resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== is-absolute-url@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-2.1.0.tgz#50530dfb84fcc9aa7dbe7852e83a37b93b9f2aa6" integrity sha1-UFMN+4T8yap9vnhS6Do3uTufKqY= is-accessor-descriptor@^0.1.6: version "0.1.6" resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" integrity sha1-qeEss66Nh2cn7u84Q/igiXtcmNY= dependencies: kind-of "^3.0.2" is-accessor-descriptor@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz#169c2f6d3df1f992618072365c9b0ea1f6878656" integrity sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ== dependencies: kind-of "^6.0.0" is-arrayish@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= is-arrayish@^0.3.1: version "0.3.2" resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== is-bigint@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.2.tgz#ffb381442503235ad245ea89e45b3dbff040ee5a" integrity sha512-0JV5+SOCQkIdzjBK9buARcV804Ddu7A0Qet6sHi3FimE9ne6m4BGQZfRn+NZiXbBk4F4XmHfDZIipLj9pX8dSA== is-binary-path@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898" integrity sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg= dependencies: binary-extensions "^1.0.0" is-binary-path@~2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== dependencies: binary-extensions "^2.0.0" is-boolean-object@^1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.1.tgz#3c0878f035cb821228d350d2e1e36719716a3de8" integrity sha512-bXdQWkECBUIAcCkeH1unwJLIpZYaa5VvuygSyS/c2lf719mTKZDU5UdDRlpd01UjADgmW8RfqaP+mRaVPdr/Ng== dependencies: call-bind "^1.0.2" is-buffer@^1.1.5: version "1.1.6" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== is-callable@^1.1.4, is-callable@^1.1.5, is-callable@^1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.3.tgz#8b1e0500b73a1d76c70487636f368e519de8db8e" integrity sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ== is-ci@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-2.0.0.tgz#6bc6334181810e04b5c22b3d589fdca55026404c" integrity sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w== dependencies: ci-info "^2.0.0" is-color-stop@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-color-stop/-/is-color-stop-1.1.0.tgz#cfff471aee4dd5c9e158598fbe12967b5cdad345" integrity sha1-z/9HGu5N1cnhWFmPvhKWe1za00U= dependencies: css-color-names "^0.0.4" hex-color-regex "^1.1.0" hsl-regex "^1.0.0" hsla-regex "^1.0.0" rgb-regex "^1.0.1" rgba-regex "^1.0.0" is-core-module@^2.1.0, is-core-module@^2.2.0: version "2.5.0" resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.5.0.tgz#f754843617c70bfd29b7bd87327400cda5c18491" integrity sha512-TXCMSDsEHMEEZ6eCA8rwRDbLu55MRGmrctljsBX/2v1d9/GzqHOxW5c5oPSgrUt2vBFXebu9rGqckXGPWOlYpg== dependencies: has "^1.0.3" is-data-descriptor@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" integrity sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y= dependencies: kind-of "^3.0.2" is-data-descriptor@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz#d84876321d0e7add03990406abbbbd36ba9268c7" integrity sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ== dependencies: kind-of "^6.0.0" is-date-object@^1.0.1: version "1.0.4" resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.4.tgz#550cfcc03afada05eea3dd30981c7b09551f73e5" integrity sha512-/b4ZVsG7Z5XVtIxs/h9W8nvfLgSAyKYdtGWQLbqy6jA1icmgjf8WCoTKgeS4wy5tYaPePouzFMANbnj94c2Z+A== is-deflate@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-deflate/-/is-deflate-1.0.0.tgz#c862901c3c161fb09dac7cdc7e784f80e98f2f14" integrity sha1-yGKQHDwWH7CdrHzcfnhPgOmPLxQ= is-descriptor@^0.1.0: version "0.1.6" resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca" integrity sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg== dependencies: is-accessor-descriptor "^0.1.6" is-data-descriptor "^0.1.4" kind-of "^5.0.0" is-descriptor@^1.0.0, is-descriptor@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-1.0.2.tgz#3b159746a66604b04f8c81524ba365c5f14d86ec" integrity sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg== dependencies: is-accessor-descriptor "^1.0.0" is-data-descriptor "^1.0.0" kind-of "^6.0.2" is-directory@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/is-directory/-/is-directory-0.3.1.tgz#61339b6f2475fc772fd9c9d83f5c8575dc154ae1" integrity sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE= is-docker@^2.0.0: version "2.2.1" resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== is-extendable@^0.1.0, is-extendable@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" integrity sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik= is-extendable@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-1.0.1.tgz#a7470f9e426733d81bd81e1155264e3a3507cab4" integrity sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA== dependencies: is-plain-object "^2.0.4" is-extglob@^2.1.0, is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= is-fullwidth-code-point@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" integrity sha1-754xOG8DGn8NZDr4L95QxFfvAMs= dependencies: number-is-nan "^1.0.0" is-fullwidth-code-point@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= is-fullwidth-code-point@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== is-glob@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a" integrity sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo= dependencies: is-extglob "^2.1.0" is-glob@^4.0.0, is-glob@^4.0.1, is-glob@~4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== dependencies: is-extglob "^2.1.1" is-gzip@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-gzip/-/is-gzip-1.0.0.tgz#6ca8b07b99c77998025900e555ced8ed80879a83" integrity sha1-bKiwe5nHeZgCWQDlVc7Y7YCHmoM= is-installed-globally@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.4.0.tgz#9a0fd407949c30f86eb6959ef1b7994ed0b7b520" integrity sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ== dependencies: global-dirs "^3.0.0" is-path-inside "^3.0.2" is-interactive@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-interactive/-/is-interactive-1.0.0.tgz#cea6e6ae5c870a7b0a0004070b7b587e0252912e" integrity sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w== is-lambda@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-lambda/-/is-lambda-1.0.1.tgz#3d9877899e6a53efc0160504cde15f82e6f061d5" integrity sha1-PZh3iZ5qU+/AFgUEzeFfgubwYdU= is-module@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-module/-/is-module-1.0.0.tgz#3258fb69f78c14d5b815d664336b4cffb6441591" integrity sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE= is-negative-zero@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.1.tgz#3de746c18dda2319241a53675908d8f766f11c24" integrity sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w== is-npm@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-5.0.0.tgz#43e8d65cc56e1b67f8d47262cf667099193f45a8" integrity sha512-WW/rQLOazUq+ST/bCAVBp/2oMERWLsR7OrKyt052dNDk4DHcDE0/7QSXITlmi+VBcV13DfIbysG3tZJm5RfdBA== is-number-object@^1.0.4: version "1.0.5" resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.5.tgz#6edfaeed7950cff19afedce9fbfca9ee6dd289eb" integrity sha512-RU0lI/n95pMoUKu9v1BZP5MBcZuNSVJkMkAG2dJqC4z2GlkGUNeH68SuHuBKBD/XFe+LHZ+f9BKkLET60Niedw== is-number@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" integrity sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU= dependencies: kind-of "^3.0.2" is-number@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== is-obj@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f" integrity sha1-PkcprB9f3gJc19g6iW2rn09n2w8= is-obj@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982" integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w== is-path-cwd@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-2.2.0.tgz#67d43b82664a7b5191fd9119127eb300048a9fdb" integrity sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ== is-path-inside@^3.0.2: version "3.0.3" resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== is-plain-obj@2.1.0, is-plain-obj@^2.0.0, is-plain-obj@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287" integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA== is-plain-obj@^1.0.0, is-plain-obj@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" integrity sha1-caUMhCnfync8kqOQpKA7OfzVHT4= is-plain-object@^2.0.3, is-plain-object@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== dependencies: isobject "^3.0.1" is-plain-object@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-5.0.0.tgz#4427f50ab3429e9025ea7d52e9043a9ef4159344" integrity sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q== is-promise@^2.1.0, is-promise@^2.2.2: version "2.2.2" resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.2.2.tgz#39ab959ccbf9a774cf079f7b40c7a26f763135f1" integrity sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ== is-regex@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.3.tgz#d029f9aff6448b93ebbe3f33dac71511fdcbef9f" integrity sha512-qSVXFz28HM7y+IWX6vLCsexdlvzT1PJNFSBuaQLQ5o0IEw8UDYW6/2+eCMVyIsbM8CNLX2a/QWmSpyxYEHY7CQ== dependencies: call-bind "^1.0.2" has-symbols "^1.0.2" is-regexp@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-regexp/-/is-regexp-1.0.0.tgz#fd2d883545c46bac5a633e7b9a09e87fa2cb5069" integrity sha1-/S2INUXEa6xaYz57mgnof6LLUGk= is-resolvable@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.1.0.tgz#fb18f87ce1feb925169c9a407c19318a3206ed88" integrity sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg== is-ssh@^1.3.0: version "1.3.3" resolved "https://registry.yarnpkg.com/is-ssh/-/is-ssh-1.3.3.tgz#7f133285ccd7f2c2c7fc897b771b53d95a2b2c7e" integrity sha512-NKzJmQzJfEEma3w5cJNcUMxoXfDjz0Zj0eyCalHn2E6VOwlzjZo0yuO2fcBSf8zhFuVCL/82/r5gRcoi6aEPVQ== dependencies: protocols "^1.1.0" is-stream@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= is-stream@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== is-string@^1.0.5, is-string@^1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.6.tgz#3fe5d5992fb0d93404f32584d4b0179a71b54a5f" integrity sha512-2gdzbKUuqtQ3lYNrUTQYoClPhm7oQu4UdpSZMp1/DGgkHBT8E2Z1l0yMdb6D4zNAxwDiMv8MdulKROJGNl0Q0w== is-symbol@^1.0.2, is-symbol@^1.0.3: version "1.0.4" resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c" integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg== dependencies: has-symbols "^1.0.2" is-text-path@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-text-path/-/is-text-path-1.0.1.tgz#4e1aa0fb51bfbcb3e92688001397202c1775b66e" integrity sha1-Thqg+1G/vLPpJogAE5cgLBd1tm4= dependencies: text-extensions "^1.0.0" is-typedarray@^1.0.0, is-typedarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= is-unicode-supported@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== is-windows@^1.0.1, is-windows@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== is-wsl@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d" integrity sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0= is-wsl@^2.1.1, is-wsl@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== dependencies: is-docker "^2.0.0" is-yarn-global@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/is-yarn-global/-/is-yarn-global-0.3.0.tgz#d502d3382590ea3004893746754c89139973e232" integrity sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw== is@^3.2.1: version "3.3.0" resolved "https://registry.yarnpkg.com/is/-/is-3.3.0.tgz#61cff6dd3c4193db94a3d62582072b44e5645d79" integrity sha512-nW24QBoPcFGGHJGUwnfpI7Yc5CdqWNdsyHQszVE/z2pKHXzh7FZ5GWhJqSyaQ9wMkQnsTx+kAI8bHlCX4tKdbg== isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= isbinaryfile@^4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/isbinaryfile/-/isbinaryfile-4.0.8.tgz#5d34b94865bd4946633ecc78a026fc76c5b11fcf" integrity sha512-53h6XFniq77YdW+spoRrebh0mnmTxRPTlcuIArO57lmMdq4uBKFKaeTjnb92oYWrSn/LVL+LT+Hap2tFQj8V+w== isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= isobject@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" integrity sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk= dependencies: isarray "1.0.0" isobject@^3.0.0, isobject@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= isstream@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= jju@~1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/jju/-/jju-1.4.0.tgz#a3abe2718af241a2b2904f84a625970f389ae32a" integrity sha1-o6vicYryQaKykE+EpiWXDzia4yo= jquery@^3.4.1: version "3.6.0" resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.6.0.tgz#c72a09f15c1bdce142f49dbf1170bdf8adac2470" integrity sha512-JVzAR/AjBvVt2BmYhxRCSYysDsPcssdmTFnzyLEts9qNwmjmu4JTAMYubEfwVOSwpQ1I1sKKFcxhZCI2buerfw== js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== js-yaml@4.1.0, js-yaml@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== dependencies: argparse "^2.0.1" js-yaml@^3.10.0, js-yaml@^3.13.1: version "3.14.1" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== dependencies: argparse "^1.0.7" esprima "^4.0.0" jsbn@~0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= jsdom@15.2.1: version "15.2.1" resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-15.2.1.tgz#d2feb1aef7183f86be521b8c6833ff5296d07ec5" integrity sha512-fAl1W0/7T2G5vURSyxBzrJ1LSdQn6Tr5UX/xD4PXDx/PDgwygedfW6El/KIj3xJ7FU61TTYnc/l/B7P49Eqt6g== dependencies: abab "^2.0.0" acorn "^7.1.0" acorn-globals "^4.3.2" array-equal "^1.0.0" cssom "^0.4.1" cssstyle "^2.0.0" data-urls "^1.1.0" domexception "^1.0.1" escodegen "^1.11.1" html-encoding-sniffer "^1.0.2" nwsapi "^2.2.0" parse5 "5.1.0" pn "^1.1.0" request "^2.88.0" request-promise-native "^1.0.7" saxes "^3.1.9" symbol-tree "^3.2.2" tough-cookie "^3.0.1" w3c-hr-time "^1.0.1" w3c-xmlserializer "^1.1.2" webidl-conversions "^4.0.2" whatwg-encoding "^1.0.5" whatwg-mimetype "^2.3.0" whatwg-url "^7.0.0" ws "^7.0.0" xml-name-validator "^3.0.0" json-buffer@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898" integrity sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg= json-buffer@3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== json-file-plus@^3.3.1: version "3.3.1" resolved "https://registry.yarnpkg.com/json-file-plus/-/json-file-plus-3.3.1.tgz#f4363806b82819ff8803d83d539d6a9edd2a5258" integrity sha512-wo0q1UuiV5NsDPQDup1Km8IwEeqe+olr8tkWxeJq9Bjtcp7DZ0l+yrg28fSC3DEtrE311mhTZ54QGS6oiqnZEA== dependencies: is "^3.2.1" node.extend "^2.0.0" object.assign "^4.1.0" promiseback "^2.0.2" safer-buffer "^2.0.2" json-parse-better-errors@^1.0.1, json-parse-better-errors@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== json-parse-even-better-errors@^2.3.0: version "2.3.1" resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== json-schema-traverse@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== json-schema@0.2.3: version "0.2.3" resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM= json-stable-stringify-without-jsonify@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= json5@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe" integrity sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow== dependencies: minimist "^1.2.0" json5@^2.1.2: version "2.2.0" resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.0.tgz#2dfefe720c6ba525d9ebd909950f0515316c89a3" integrity sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA== dependencies: minimist "^1.2.5" jsonfile@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" integrity sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss= optionalDependencies: graceful-fs "^4.1.6" jsonfile@^6.0.1: version "6.1.0" resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae" integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ== dependencies: universalify "^2.0.0" optionalDependencies: graceful-fs "^4.1.6" jsonparse@^1.2.0, jsonparse@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280" integrity sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA= jsonwebtoken@8.5.1: version "8.5.1" resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz#00e71e0b8df54c2121a1f26137df2280673bcc0d" integrity sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w== dependencies: jws "^3.2.2" lodash.includes "^4.3.0" lodash.isboolean "^3.0.3" lodash.isinteger "^4.0.4" lodash.isnumber "^3.0.3" lodash.isplainobject "^4.0.6" lodash.isstring "^4.0.1" lodash.once "^4.0.0" ms "^2.1.1" semver "^5.6.0" jsprim@^1.2.2: version "1.4.1" resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" integrity sha1-MT5mvB5cwG5Di8G3SZwuXFastqI= dependencies: assert-plus "1.0.0" extsprintf "1.3.0" json-schema "0.2.3" verror "1.10.0" jszip@3.7.0, jszip@^3.7.0: version "3.7.0" resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.7.0.tgz#9b8b995a4e7c9024653ce743e902076a82fdf4e6" integrity sha512-Y2OlFIzrDOPWUnpU0LORIcDn2xN7rC9yKffFM/7pGhQuhO+SUhfm2trkJ/S5amjFvem0Y+1EALz/MEPkvHXVNw== dependencies: lie "~3.3.0" pako "~1.0.2" readable-stream "~2.3.6" set-immediate-shim "~1.0.1" jwa@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a" integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA== dependencies: buffer-equal-constant-time "1.0.1" ecdsa-sig-formatter "1.0.11" safe-buffer "^5.0.1" jws@^3.2.2: version "3.2.2" resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304" integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA== dependencies: jwa "^1.4.1" safe-buffer "^5.0.1" karma-chrome-launcher@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/karma-chrome-launcher/-/karma-chrome-launcher-3.1.0.tgz#805a586799a4d05f4e54f72a204979f3f3066738" integrity sha512-3dPs/n7vgz1rxxtynpzZTvb9y/GIaW8xjAwcIGttLbycqoFtI7yo1NGnQi6oFTherRE+GIhCAHZC4vEqWGhNvg== dependencies: which "^1.2.1" karma-firefox-launcher@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/karma-firefox-launcher/-/karma-firefox-launcher-2.1.1.tgz#6457226f8e4f091b664cef79bb5d39bf1e008765" integrity sha512-VzDMgPseXak9DtfyE1O5bB2BwsMy1zzO1kUxVW1rP0yhC4tDNJ0p3JoFdzvrK4QqVzdqUMa9Rx9YzkdFp8hz3Q== dependencies: is-wsl "^2.2.0" which "^2.0.1" karma-ie-launcher@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/karma-ie-launcher/-/karma-ie-launcher-1.0.0.tgz#497986842c490190346cd89f5494ca9830c6d59c" integrity sha1-SXmGhCxJAZA0bNifVJTKmDDG1Zw= dependencies: lodash "^4.6.1" karma-mocha-reporter@^2.2.5: version "2.2.5" resolved "https://registry.yarnpkg.com/karma-mocha-reporter/-/karma-mocha-reporter-2.2.5.tgz#15120095e8ed819186e47a0b012f3cd741895560" integrity sha1-FRIAlejtgZGG5HoLAS8810GJVWA= dependencies: chalk "^2.1.0" log-symbols "^2.1.0" strip-ansi "^4.0.0" karma-mocha@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/karma-mocha/-/karma-mocha-2.0.1.tgz#4b0254a18dfee71bdbe6188d9a6861bf86b0cd7d" integrity sha512-Tzd5HBjm8his2OA4bouAsATYEpZrp9vC7z5E5j4C5Of5Rrs1jY67RAwXNcVmd/Bnk1wgvQRou0zGVLey44G4tQ== dependencies: minimist "^1.2.3" karma@^6.3.4: version "6.3.4" resolved "https://registry.yarnpkg.com/karma/-/karma-6.3.4.tgz#359899d3aab3d6b918ea0f57046fd2a6b68565e6" integrity sha512-hbhRogUYIulfkBTZT7xoPrCYhRBnBoqbbL4fszWD0ReFGUxU+LYBr3dwKdAluaDQ/ynT9/7C+Lf7pPNW4gSx4Q== dependencies: body-parser "^1.19.0" braces "^3.0.2" chokidar "^3.5.1" colors "^1.4.0" connect "^3.7.0" di "^0.0.1" dom-serialize "^2.2.1" glob "^7.1.7" graceful-fs "^4.2.6" http-proxy "^1.18.1" isbinaryfile "^4.0.8" lodash "^4.17.21" log4js "^6.3.0" mime "^2.5.2" minimatch "^3.0.4" qjobs "^1.2.0" range-parser "^1.2.1" rimraf "^3.0.2" socket.io "^3.1.0" source-map "^0.6.1" tmp "^0.2.1" ua-parser-js "^0.7.28" yargs "^16.1.1" keygrip@~1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/keygrip/-/keygrip-1.1.0.tgz#871b1681d5e159c62a445b0c74b615e0917e7226" integrity sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ== dependencies: tsscmp "1.0.6" keyv@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.1.0.tgz#ecc228486f69991e49e9476485a5be1e8fc5c4d9" integrity sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA== dependencies: json-buffer "3.0.0" keyv@^4.0.0: version "4.0.3" resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.0.3.tgz#4f3aa98de254803cafcd2896734108daa35e4254" integrity sha512-zdGa2TOpSZPq5mU6iowDARnMBZgtCqJ11dJROFi6tg6kTn4nuUdU09lFyLFSaHrWqpIJ+EBq4E8/Dc0Vx5vLdA== dependencies: json-buffer "3.0.1" kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: version "3.2.2" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" integrity sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ= dependencies: is-buffer "^1.1.5" kind-of@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57" integrity sha1-IIE989cSkosgc3hpGkUGb65y3Vc= dependencies: is-buffer "^1.1.5" kind-of@^5.0.0: version "5.1.0" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d" integrity sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw== kind-of@^6.0.0, kind-of@^6.0.2, kind-of@^6.0.3: version "6.0.3" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== kleur@4.1.4: version "4.1.4" resolved "https://registry.yarnpkg.com/kleur/-/kleur-4.1.4.tgz#8c202987d7e577766d039a8cd461934c01cda04d" integrity sha512-8QADVssbrFjivHWQU7KkMgptGTl6WAcSdlbBPY4uNF+mWr6DGcKrvY2w4FQJoXch7+fKMjj0dRrL75vk3k23OA== latest-version@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-5.1.0.tgz#119dfe908fe38d15dfa43ecd13fa12ec8832face" integrity sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA== dependencies: package-json "^6.3.0" lerna@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/lerna/-/lerna-4.0.0.tgz#b139d685d50ea0ca1be87713a7c2f44a5b678e9e" integrity sha512-DD/i1znurfOmNJb0OBw66NmNqiM8kF6uIrzrJ0wGE3VNdzeOhz9ziWLYiRaZDGGwgbcjOo6eIfcx9O5Qynz+kg== dependencies: "@lerna/add" "4.0.0" "@lerna/bootstrap" "4.0.0" "@lerna/changed" "4.0.0" "@lerna/clean" "4.0.0" "@lerna/cli" "4.0.0" "@lerna/create" "4.0.0" "@lerna/diff" "4.0.0" "@lerna/exec" "4.0.0" "@lerna/import" "4.0.0" "@lerna/info" "4.0.0" "@lerna/init" "4.0.0" "@lerna/link" "4.0.0" "@lerna/list" "4.0.0" "@lerna/publish" "4.0.0" "@lerna/run" "4.0.0" "@lerna/version" "4.0.0" import-local "^3.0.2" npmlog "^4.1.2" levn@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== dependencies: prelude-ls "^1.2.1" type-check "~0.4.0" levn@~0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" integrity sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4= dependencies: prelude-ls "~1.1.2" type-check "~0.3.2" libnpmaccess@^4.0.1: version "4.0.3" resolved "https://registry.yarnpkg.com/libnpmaccess/-/libnpmaccess-4.0.3.tgz#dfb0e5b0a53c315a2610d300e46b4ddeb66e7eec" integrity sha512-sPeTSNImksm8O2b6/pf3ikv4N567ERYEpeKRPSmqlNt1dTZbvgpJIzg5vAhXHpw2ISBsELFRelk0jEahj1c6nQ== dependencies: aproba "^2.0.0" minipass "^3.1.1" npm-package-arg "^8.1.2" npm-registry-fetch "^11.0.0" libnpmpublish@^4.0.0: version "4.0.2" resolved "https://registry.yarnpkg.com/libnpmpublish/-/libnpmpublish-4.0.2.tgz#be77e8bf5956131bcb45e3caa6b96a842dec0794" integrity sha512-+AD7A2zbVeGRCFI2aO//oUmapCwy7GHqPXFJh3qpToSRNU+tXKJ2YFUgjt04LPPAf2dlEH95s6EhIHM1J7bmOw== dependencies: normalize-package-data "^3.0.2" npm-package-arg "^8.1.2" npm-registry-fetch "^11.0.0" semver "^7.1.3" ssri "^8.0.1" lie@~3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/lie/-/lie-3.3.0.tgz#dcf82dee545f46074daf200c7c1c5a08e0f40f6a" integrity sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ== dependencies: immediate "~3.0.5" lilconfig@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.0.3.tgz#68f3005e921dafbd2a2afb48379986aa6d2579fd" integrity sha512-EHKqr/+ZvdKCifpNrJCKxBTgk5XupZA3y/aCPY9mxfgBzmgh93Mt/WqjjQ38oMxXuvDokaKiM3lAgvSH2sjtHg== lines-and-columns@^1.1.6: version "1.1.6" resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA= lint-staged@^10.2.13: version "10.5.4" resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-10.5.4.tgz#cd153b5f0987d2371fc1d2847a409a2fe705b665" integrity sha512-EechC3DdFic/TdOPgj/RB3FicqE6932LTHCUm0Y2fsD9KGlLB+RwJl2q1IYBIvEsKzDOgn0D4gll+YxG5RsrKg== dependencies: chalk "^4.1.0" cli-truncate "^2.1.0" commander "^6.2.0" cosmiconfig "^7.0.0" debug "^4.2.0" dedent "^0.7.0" enquirer "^2.3.6" execa "^4.1.0" listr2 "^3.2.2" log-symbols "^4.0.0" micromatch "^4.0.2" normalize-path "^3.0.0" please-upgrade-node "^3.2.0" string-argv "0.3.1" stringify-object "^3.3.0" listr2@^3.2.2: version "3.12.1" resolved "https://registry.yarnpkg.com/listr2/-/listr2-3.12.1.tgz#75e515b86c66b60baf253542cc0dced6b60fedaf" integrity sha512-oB1DlXlCzGPbvWhqYBZUQEPJKqsmebQWofXG6Mpbe3uIvoNl8mctBEojyF13ZyqwQ91clCWXpwsWp+t98K4FOQ== dependencies: cli-truncate "^2.1.0" colorette "^1.4.0" log-update "^4.0.0" p-map "^4.0.0" rxjs "^6.6.7" through "^2.3.8" wrap-ansi "^7.0.0" load-json-file@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-4.0.0.tgz#2f5f45ab91e33216234fd53adab668eb4ec0993b" integrity sha1-L19Fq5HjMhYjT9U62rZo607AmTs= dependencies: graceful-fs "^4.1.2" parse-json "^4.0.0" pify "^3.0.0" strip-bom "^3.0.0" load-json-file@^6.2.0: version "6.2.0" resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-6.2.0.tgz#5c7770b42cafa97074ca2848707c61662f4251a1" integrity sha512-gUD/epcRms75Cw8RT1pUdHugZYM5ce64ucs2GEISABwkRsOQr0q2wm/MV2TKThycIe5e0ytRweW2RZxclogCdQ== dependencies: graceful-fs "^4.1.15" parse-json "^5.0.0" strip-bom "^4.0.0" type-fest "^0.6.0" loader-runner@^2.4.0: version "2.4.0" resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.4.0.tgz#ed47066bfe534d7e84c4c7b9998c2a75607d9357" integrity sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw== loader-utils@^1.1.0, loader-utils@^1.2.3, loader-utils@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.0.tgz#c579b5e34cb34b1a74edc6c1fb36bfa371d5a613" integrity sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA== dependencies: big.js "^5.2.2" emojis-list "^3.0.0" json5 "^1.0.1" loader-utils@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.0.tgz#e4cace5b816d425a166b5f097e10cd12b36064b0" integrity sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ== dependencies: big.js "^5.2.2" emojis-list "^3.0.0" json5 "^2.1.2" locate-path@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" integrity sha1-K1aLJl7slExtnA3pw9u7ygNUzY4= dependencies: p-locate "^2.0.0" path-exists "^3.0.0" locate-path@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A== dependencies: p-locate "^3.0.0" path-exists "^3.0.0" locate-path@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== dependencies: p-locate "^4.1.0" locate-path@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== dependencies: p-locate "^5.0.0" lockfile@1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/lockfile/-/lockfile-1.0.4.tgz#07f819d25ae48f87e538e6578b6964a4981a5609" integrity sha512-cvbTwETRfsFh4nHsL1eGWapU1XFi5Ot9E85sWAwia7Y7EgB7vfqcZhTKZ+l7hCGxSPoushMv5GKhT5PdLv03WA== dependencies: signal-exit "^3.0.2" lodash._reinterpolate@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d" integrity sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0= lodash.assign@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-4.2.0.tgz#0d99f3ccd7a6d261d19bdaeb9245005d285808e7" integrity sha1-DZnzzNem0mHRm9rrkkUAXShYCOc= lodash.assignin@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/lodash.assignin/-/lodash.assignin-4.2.0.tgz#ba8df5fb841eb0a3e8044232b0e263a8dc6a28a2" integrity sha1-uo31+4QesKPoBEIysOJjqNxqKKI= lodash.camelcase@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" integrity sha1-soqmKIorn8ZRA1x3EfZathkDMaY= lodash.chunk@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/lodash.chunk/-/lodash.chunk-4.2.0.tgz#66e5ce1f76ed27b4303d8c6512e8d1216e8106bc" integrity sha1-ZuXOH3btJ7QwPYxlEujRIW6BBrw= lodash.clone@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.clone/-/lodash.clone-4.5.0.tgz#195870450f5a13192478df4bc3d23d2dea1907b6" integrity sha1-GVhwRQ9aExkkeN9Lw9I9LeoZB7Y= lodash.clonedeep@^4.3.0, lodash.clonedeep@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8= lodash.constant@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/lodash.constant/-/lodash.constant-3.0.0.tgz#bfe05cce7e515b3128925d6362138420bd624910" integrity sha1-v+Bczn5RWzEokl1jYhOEIL1iSRA= lodash.defaults@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c" integrity sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw= lodash.filter@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash.filter/-/lodash.filter-4.6.0.tgz#668b1d4981603ae1cc5a6fa760143e480b4c4ace" integrity sha1-ZosdSYFgOuHMWm+nYBQ+SAtMSs4= lodash.find@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash.find/-/lodash.find-4.6.0.tgz#cb0704d47ab71789ffa0de8b97dd926fb88b13b1" integrity sha1-ywcE1Hq3F4n/oN6Ll92Sb7iLE7E= lodash.findindex@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash.findindex/-/lodash.findindex-4.6.0.tgz#a3245dee61fb9b6e0624b535125624bb69c11106" integrity sha1-oyRd7mH7m24GJLU1ElYku2nBEQY= lodash.findkey@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash.findkey/-/lodash.findkey-4.6.0.tgz#83058e903b51cbb759d09ccf546dea3ea39c4718" integrity sha1-gwWOkDtRy7dZ0JzPVG3qPqOcRxg= lodash.flatmap@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.flatmap/-/lodash.flatmap-4.5.0.tgz#ef8cbf408f6e48268663345305c6acc0b778702e" integrity sha1-74y/QI9uSCaGYzRTBcaswLd4cC4= lodash.flatten@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f" integrity sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8= lodash.flattendeep@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz#fb030917f86a3134e5bc9bec0d69e0013ddfedb2" integrity sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI= lodash.foreach@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.foreach/-/lodash.foreach-4.5.0.tgz#1a6a35eace401280c7f06dddec35165ab27e3e53" integrity sha1-Gmo16s5AEoDH8G3d7DUWWrJ+PlM= lodash.get@^4.0.0, lodash.get@^4.4.2: version "4.4.2" resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk= lodash.groupby@4.6.0, lodash.groupby@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash.groupby/-/lodash.groupby-4.6.0.tgz#0b08a1dcf68397c397855c3239783832df7403d1" integrity sha1-Cwih3PaDl8OXhVwyOXg4Mt90A9E= lodash.has@^4.5.2: version "4.5.2" resolved "https://registry.yarnpkg.com/lodash.has/-/lodash.has-4.5.2.tgz#d19f4dc1095058cccbe2b0cdf4ee0fe4aa37c862" integrity sha1-0Z9NwQlQWMzL4rDN9O4P5Ko3yGI= lodash.includes@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" integrity sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8= lodash.invert@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/lodash.invert/-/lodash.invert-4.3.0.tgz#8ffe20d4b616f56bea8f1aa0c6ebd80dcf742aee" integrity sha1-j/4g1LYW9WvqjxqgxuvYDc90Ku4= lodash.isboolean@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" integrity sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY= lodash.isempty@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.isempty/-/lodash.isempty-4.4.0.tgz#6f86cbedd8be4ec987be9aaf33c9684db1b31e7e" integrity sha1-b4bL7di+TsmHvpqvM8loTbGzHn4= lodash.isequal@^4.0.0, lodash.isequal@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" integrity sha1-QVxEePK8wwEgwizhDtMib30+GOA= lodash.isfunction@^3.0.9: version "3.0.9" resolved "https://registry.yarnpkg.com/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz#06de25df4db327ac931981d1bdb067e5af68d051" integrity sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw== lodash.isinteger@^4.0.4: version "4.0.4" resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343" integrity sha1-YZwK89A/iwTDH1iChAt3sRzWg0M= lodash.ismatch@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz#756cb5150ca3ba6f11085a78849645f188f85f37" integrity sha1-dWy1FQyjum8RCFp4hJZF8Yj4Xzc= lodash.isnumber@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc" integrity sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w= lodash.isobject@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/lodash.isobject/-/lodash.isobject-3.0.2.tgz#3c8fb8d5b5bf4bf90ae06e14f2a530a4ed935e1d" integrity sha1-PI+41bW/S/kK4G4U8qUwpO2TXh0= lodash.isplainobject@^4.0.6: version "4.0.6" resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" integrity sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs= lodash.isstring@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" integrity sha1-1SfftUVuynzJu5XV2ur4i6VKVFE= lodash.isundefined@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/lodash.isundefined/-/lodash.isundefined-3.0.1.tgz#23ef3d9535565203a66cefd5b830f848911afb48" integrity sha1-I+89lTVWUgOmbO/VuDD4SJEa+0g= lodash.keys@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/lodash.keys/-/lodash.keys-4.2.0.tgz#a08602ac12e4fb83f91fc1fb7a360a4d9ba35205" integrity sha1-oIYCrBLk+4P5H8H7ejYKTZujUgU= lodash.last@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/lodash.last/-/lodash.last-3.0.0.tgz#242f663112dd4c6e63728c60a3c909d1bdadbd4c" integrity sha1-JC9mMRLdTG5jcoxgo8kJ0b2tvUw= lodash.map@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash.map/-/lodash.map-4.6.0.tgz#771ec7839e3473d9c4cde28b19394c3562f4f6d3" integrity sha1-dx7Hg540c9nEzeKLGTlMNWL09tM= lodash.memoize@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4= lodash.merge@^4.6.2: version "4.6.2" resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== lodash.omit@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.omit/-/lodash.omit-4.5.0.tgz#6eb19ae5a1ee1dd9df0b969e66ce0b7fa30b5e60" integrity sha1-brGa5aHuHdnfC5aeZs4Lf6MLXmA= lodash.once@^4.0.0: version "4.1.1" resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w= lodash.orderby@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash.orderby/-/lodash.orderby-4.6.0.tgz#e697f04ce5d78522f54d9338b32b81a3393e4eb3" integrity sha1-5pfwTOXXhSL1TZM4syuBozk+TrM= lodash.pick@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.pick/-/lodash.pick-4.4.0.tgz#52f05610fff9ded422611441ed1fc123a03001b3" integrity sha1-UvBWEP/53tQiYRRB7R/BI6AwAbM= lodash.reduce@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash.reduce/-/lodash.reduce-4.6.0.tgz#f1ab6b839299ad48f784abbf476596f03b914d3b" integrity sha1-8atrg5KZrUj3hKu/R2WW8DuRTTs= lodash.set@^4.3.2: version "4.3.2" resolved "https://registry.yarnpkg.com/lodash.set/-/lodash.set-4.3.2.tgz#d8757b1da807dde24816b0d6a84bea1a76230b23" integrity sha1-2HV7HagH3eJIFrDWqEvqGnYjCyM= lodash.size@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/lodash.size/-/lodash.size-4.2.0.tgz#71fe75ed3eabdb2bcb73a1b0b4f51c392ee27b86" integrity sha1-cf517T6r2yvLc6GwtPUcOS7ie4Y= lodash.sortby@^4.7.0: version "4.7.0" resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438" integrity sha1-7dFMgk4sycHgsKG0K7UhBRakJDg= lodash.sum@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/lodash.sum/-/lodash.sum-4.0.2.tgz#ad90e397965d803d4f1ff7aa5b2d0197f3b4637b" integrity sha1-rZDjl5ZdgD1PH/eqWy0Bl/O0Y3s= lodash.template@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-4.5.0.tgz#f976195cf3f347d0d5f52483569fe8031ccce8ab" integrity sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A== dependencies: lodash._reinterpolate "^3.0.0" lodash.templatesettings "^4.0.0" lodash.templatesettings@^4.0.0: version "4.2.0" resolved "https://registry.yarnpkg.com/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz#e481310f049d3cf6d47e912ad09313b154f0fb33" integrity sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ== dependencies: lodash._reinterpolate "^3.0.0" lodash.topairs@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/lodash.topairs/-/lodash.topairs-4.3.0.tgz#3b6deaa37d60fb116713c46c5f17ea190ec48d64" integrity sha1-O23qo31g+xFnE8RsXxfqGQ7EjWQ= lodash.transform@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash.transform/-/lodash.transform-4.6.0.tgz#12306422f63324aed8483d3f38332b5f670547a0" integrity sha1-EjBkIvYzJK7YSD0/ODMrX2cFR6A= lodash.union@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash.union/-/lodash.union-4.6.0.tgz#48bb5088409f16f1821666641c44dd1aaae3cd88" integrity sha1-SLtQiECfFvGCFmZkHETdGqrjzYg= lodash.uniq@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= lodash.upperfirst@^4.3.1: version "4.3.1" resolved "https://registry.yarnpkg.com/lodash.upperfirst/-/lodash.upperfirst-4.3.1.tgz#1365edf431480481ef0d1c68957a5ed99d49f7ce" integrity sha1-E2Xt9DFIBIHvDRxolXpe2Z1J984= lodash.values@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/lodash.values/-/lodash.values-4.3.0.tgz#a3a6c2b0ebecc5c2cba1c17e6e620fe81b53d347" integrity sha1-o6bCsOvsxcLLocF+bmIP6BtT00c= lodash@4, lodash@4.17.21, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.21, lodash@^4.6.1, lodash@^4.7.0, lodash@~4.17.15: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== log-symbols@4.1.0, log-symbols@^4.0.0, log-symbols@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== dependencies: chalk "^4.1.0" is-unicode-supported "^0.1.0" log-symbols@^2.1.0: version "2.2.0" resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-2.2.0.tgz#5740e1c5d6f0dfda4ad9323b5332107ef6b4c40a" integrity sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg== dependencies: chalk "^2.0.1" log-update@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/log-update/-/log-update-4.0.0.tgz#589ecd352471f2a1c0c570287543a64dfd20e0a1" integrity sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg== dependencies: ansi-escapes "^4.3.0" cli-cursor "^3.1.0" slice-ansi "^4.0.0" wrap-ansi "^6.2.0" log4js@^6.3.0: version "6.3.0" resolved "https://registry.yarnpkg.com/log4js/-/log4js-6.3.0.tgz#10dfafbb434351a3e30277a00b9879446f715bcb" integrity sha512-Mc8jNuSFImQUIateBFwdOQcmC6Q5maU0VVvdC2R6XMb66/VnT+7WS4D/0EeNMZu1YODmJe5NIn2XftCzEocUgw== dependencies: date-format "^3.0.0" debug "^4.1.1" flatted "^2.0.1" rfdc "^1.1.4" streamroller "^2.2.4" lowdb@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/lowdb/-/lowdb-1.0.0.tgz#5243be6b22786ccce30e50c9a33eac36b20c8064" integrity sha512-2+x8esE/Wb9SQ1F9IHaYWfsC9FIecLOPrK4g17FGEayjUWH172H6nwicRovGvSE2CPZouc2MCIqCI7h9d+GftQ== dependencies: graceful-fs "^4.1.3" is-promise "^2.1.0" lodash "4" pify "^3.0.0" steno "^0.4.1" lowercase-keys@^1.0.0, lowercase-keys@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" integrity sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA== lowercase-keys@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479" integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA== lru-cache@6.0.0, lru-cache@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== dependencies: yallist "^4.0.0" lru-cache@^4.0.0: version "4.1.5" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd" integrity sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g== dependencies: pseudomap "^1.0.2" yallist "^2.1.2" lru-cache@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== dependencies: yallist "^3.0.2" lru-queue@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/lru-queue/-/lru-queue-0.1.0.tgz#2738bd9f0d3cf4f84490c5736c48699ac632cda3" integrity sha1-Jzi9nw089PhEkMVzbEhpmsYyzaM= dependencies: es5-ext "~0.10.2" lunr-mutable-indexes@2.3.2: version "2.3.2" resolved "https://registry.yarnpkg.com/lunr-mutable-indexes/-/lunr-mutable-indexes-2.3.2.tgz#864253489735d598c5140f3fb75c0a5c8be2e98c" integrity sha512-Han6cdWAPPFM7C2AigS2Ofl3XjAT0yVMrUixodJEpyg71zCtZ2yzXc3s+suc/OaNt4ca6WJBEzVnEIjxCTwFMw== dependencies: lunr ">= 2.3.0 < 2.4.0" "lunr@>= 2.3.0 < 2.4.0", lunr@^2.3.8: version "2.3.9" resolved "https://registry.yarnpkg.com/lunr/-/lunr-2.3.9.tgz#18b123142832337dd6e964df1a5a7707b25d35e1" integrity sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow== macos-release@^2.2.0: version "2.5.0" resolved "https://registry.yarnpkg.com/macos-release/-/macos-release-2.5.0.tgz#067c2c88b5f3fb3c56a375b2ec93826220fa1ff2" integrity sha512-EIgv+QZ9r+814gjJj0Bt5vSLJLzswGmSUbUpbi9AIr/fsN2IWFBl2NucV9PAiek+U1STK468tEkxmVYUtuAN3g== make-dir@^2.0.0, make-dir@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5" integrity sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA== dependencies: pify "^4.0.1" semver "^5.6.0" make-dir@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== dependencies: semver "^6.0.0" make-fetch-happen@^8.0.9: version "8.0.14" resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-8.0.14.tgz#aaba73ae0ab5586ad8eaa68bd83332669393e222" integrity sha512-EsS89h6l4vbfJEtBZnENTOFk8mCRpY5ru36Xe5bcX1KYIli2mkSHqoFsp5O1wMDvTJJzxe/4THpCTtygjeeGWQ== dependencies: agentkeepalive "^4.1.3" cacache "^15.0.5" http-cache-semantics "^4.1.0" http-proxy-agent "^4.0.1" https-proxy-agent "^5.0.0" is-lambda "^1.0.1" lru-cache "^6.0.0" minipass "^3.1.3" minipass-collect "^1.0.2" minipass-fetch "^1.3.2" minipass-flush "^1.0.5" minipass-pipeline "^1.2.4" promise-retry "^2.0.1" socks-proxy-agent "^5.0.0" ssri "^8.0.0" make-fetch-happen@^9.0.1: version "9.0.4" resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-9.0.4.tgz#ceaa100e60e0ef9e8d1ede94614bb2ba83c8bb24" integrity sha512-sQWNKMYqSmbAGXqJg2jZ+PmHh5JAybvwu0xM8mZR/bsTjGiTASj3ldXJV7KFHy1k/IJIBkjxQFoWIVsv9+PQMg== dependencies: agentkeepalive "^4.1.3" cacache "^15.2.0" http-cache-semantics "^4.1.0" http-proxy-agent "^4.0.1" https-proxy-agent "^5.0.0" is-lambda "^1.0.1" lru-cache "^6.0.0" minipass "^3.1.3" minipass-collect "^1.0.2" minipass-fetch "^1.3.2" minipass-flush "^1.0.5" minipass-pipeline "^1.2.4" negotiator "^0.6.2" promise-retry "^2.0.1" socks-proxy-agent "^5.0.0" ssri "^8.0.0" map-cache@^0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" integrity sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8= map-obj@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d" integrity sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0= map-obj@^4.0.0: version "4.2.1" resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-4.2.1.tgz#e4ea399dbc979ae735c83c863dd31bdf364277b7" integrity sha512-+WA2/1sPmDj1dlvvJmB5G6JKfY9dpn7EVBUL06+y6PoljPkh+6V1QihwxNkbcGxCRjt2b0F9K0taiCuo7MbdFQ== map-visit@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f" integrity sha1-7Nyo8TFE5mDxtb1B8S80edmN+48= dependencies: object-visit "^1.0.0" marked@2.1.3, marked@^2.0.1: version "2.1.3" resolved "https://registry.yarnpkg.com/marked/-/marked-2.1.3.tgz#bd017cef6431724fd4b27e0657f5ceb14bff3753" integrity sha512-/Q+7MGzaETqifOMWYEA7HVMaZb4XbcRfaOzcSsHZEith83KGlvaSG33u0SKu89Mj5h+T8V2hM+8O45Qc5XTgwA== marked@^0.8.0: version "0.8.2" resolved "https://registry.yarnpkg.com/marked/-/marked-0.8.2.tgz#4faad28d26ede351a7a1aaa5fec67915c869e355" integrity sha512-EGwzEeCcLniFX51DhTpmTom+dSA/MG/OBUDjnWtHbEnjAH180VzUeAw+oE4+Zv+CoYBWyRlYOTR0N8SO9R1PVw== matcher@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/matcher/-/matcher-3.0.0.tgz#bd9060f4c5b70aa8041ccc6f80368760994f30ca" integrity sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng== dependencies: escape-string-regexp "^4.0.0" md5.js@^1.3.4: version "1.3.5" resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f" integrity sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg== dependencies: hash-base "^3.0.0" inherits "^2.0.1" safe-buffer "^5.1.2" mdn-data@2.0.14: version "2.0.14" resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50" integrity sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow== mdn-data@2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.4.tgz#699b3c38ac6f1d728091a64650b65d388502fd5b" integrity sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA== media-typer@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= memoizee@0.4.15: version "0.4.15" resolved "https://registry.yarnpkg.com/memoizee/-/memoizee-0.4.15.tgz#e6f3d2da863f318d02225391829a6c5956555b72" integrity sha512-UBWmJpLZd5STPm7PMUlOw/TSy972M+z8gcyQ5veOnSDRREz/0bmpyTfKt3/51DhEBqCZQn1udM/5flcSPYhkdQ== dependencies: d "^1.0.1" es5-ext "^0.10.53" es6-weak-map "^2.0.3" event-emitter "^0.3.5" is-promise "^2.2.2" lru-queue "^0.1.0" next-tick "^1.1.0" timers-ext "^0.1.7" memory-fs@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552" integrity sha1-OpoguEYlI+RHz7x+i7gO1me/xVI= dependencies: errno "^0.1.3" readable-stream "^2.0.1" memory-fs@^0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.5.0.tgz#324c01288b88652966d161db77838720845a8e3c" integrity sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA== dependencies: errno "^0.1.3" readable-stream "^2.0.1" meow@^7.0.0: version "7.1.1" resolved "https://registry.yarnpkg.com/meow/-/meow-7.1.1.tgz#7c01595e3d337fcb0ec4e8eed1666ea95903d306" integrity sha512-GWHvA5QOcS412WCo8vwKDlTelGLsCGBVevQB5Kva961rmNfun0PCbv5+xta2kUMFJyR8/oWnn7ddeKdosbAPbA== dependencies: "@types/minimist" "^1.2.0" camelcase-keys "^6.2.2" decamelize-keys "^1.1.0" hard-rejection "^2.1.0" minimist-options "4.1.0" normalize-package-data "^2.5.0" read-pkg-up "^7.0.1" redent "^3.0.0" trim-newlines "^3.0.0" type-fest "^0.13.1" yargs-parser "^18.1.3" meow@^8.0.0: version "8.1.2" resolved "https://registry.yarnpkg.com/meow/-/meow-8.1.2.tgz#bcbe45bda0ee1729d350c03cffc8395a36c4e897" integrity sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q== dependencies: "@types/minimist" "^1.2.0" camelcase-keys "^6.2.2" decamelize-keys "^1.1.0" hard-rejection "^2.1.0" minimist-options "4.1.0" normalize-package-data "^3.0.0" read-pkg-up "^7.0.1" redent "^3.0.0" trim-newlines "^3.0.0" type-fest "^0.18.0" yargs-parser "^20.2.3" merge-descriptors@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E= merge-stream@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== merge2@^1.2.3, merge2@^1.3.0: version "1.4.1" resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== methods@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= micromatch@4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.2.tgz#4fcb0999bf9fbc2fcbdd212f6d629b9a56c39259" integrity sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q== dependencies: braces "^3.0.1" picomatch "^2.0.5" micromatch@^3.0.4, micromatch@^3.1.10, micromatch@^3.1.4: version "3.1.10" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg== dependencies: arr-diff "^4.0.0" array-unique "^0.3.2" braces "^2.3.1" define-property "^2.0.2" extend-shallow "^3.0.2" extglob "^2.0.4" fragment-cache "^0.2.1" kind-of "^6.0.2" nanomatch "^1.2.9" object.pick "^1.3.0" regex-not "^1.0.0" snapdragon "^0.8.1" to-regex "^3.0.2" micromatch@^4.0.2, micromatch@^4.0.4: version "4.0.4" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.4.tgz#896d519dfe9db25fce94ceb7a500919bf881ebf9" integrity sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg== dependencies: braces "^3.0.1" picomatch "^2.2.3" miller-rabin@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d" integrity sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA== dependencies: bn.js "^4.0.0" brorand "^1.0.1" mime-db@1.49.0, "mime-db@>= 1.43.0 < 2": version "1.49.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.49.0.tgz#f3dfde60c99e9cf3bc9701d687778f537001cbed" integrity sha512-CIc8j9URtOVApSFCQIF+VBkX1RwXp/oMMOrqdyXSBXq5RWNEsRfyj1kiRnQgmNXmHxPoFIxOroKA3zcU9P+nAA== mime-types@^2.1.12, mime-types@^2.1.25, mime-types@~2.1.19, mime-types@~2.1.24: version "2.1.32" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.32.tgz#1d00e89e7de7fe02008db61001d9e02852670fd5" integrity sha512-hJGaVS4G4c9TSMYh2n6SQAGrC4RnfU+daP8G7cSCmaqNjiOoUY0VHCMS42pxnQmVF1GWwFhbHWn3RIxCqTmZ9A== dependencies: mime-db "1.49.0" mime@1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== mime@2.5.2, mime@^2.0.3, mime@^2.5.2: version "2.5.2" resolved "https://registry.yarnpkg.com/mime/-/mime-2.5.2.tgz#6e3dc6cc2b9510643830e5f19d5cb753da5eeabe" integrity sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg== mimic-fn@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== mimic-response@^1.0.0, mimic-response@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== mimic-response@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9" integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ== min-indent@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== minimalistic-crypto-utils@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo= "minimatch@2 || 3", minimatch@3.0.4, minimatch@^3.0.0, minimatch@^3.0.4, minimatch@~3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== dependencies: brace-expansion "^1.1.7" minimist-options@4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/minimist-options/-/minimist-options-4.1.0.tgz#c0655713c53a8a2ebd77ffa247d342c40f010619" integrity sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A== dependencies: arrify "^1.0.1" is-plain-obj "^1.1.0" kind-of "^6.0.3" minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== minipass-collect@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/minipass-collect/-/minipass-collect-1.0.2.tgz#22b813bf745dc6edba2576b940022ad6edc8c617" integrity sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA== dependencies: minipass "^3.0.0" minipass-fetch@^1.3.0, minipass-fetch@^1.3.2: version "1.3.4" resolved "https://registry.yarnpkg.com/minipass-fetch/-/minipass-fetch-1.3.4.tgz#63f5af868a38746ca7b33b03393ddf8c291244fe" integrity sha512-TielGogIzbUEtd1LsjZFs47RWuHHfhl6TiCx1InVxApBAmQ8bL0dL5ilkLGcRvuyW/A9nE+Lvn855Ewz8S0PnQ== dependencies: minipass "^3.1.0" minipass-sized "^1.0.3" minizlib "^2.0.0" optionalDependencies: encoding "^0.1.12" minipass-flush@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/minipass-flush/-/minipass-flush-1.0.5.tgz#82e7135d7e89a50ffe64610a787953c4c4cbb373" integrity sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw== dependencies: minipass "^3.0.0" minipass-json-stream@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/minipass-json-stream/-/minipass-json-stream-1.0.1.tgz#7edbb92588fbfc2ff1db2fc10397acb7b6b44aa7" integrity sha512-ODqY18UZt/I8k+b7rl2AENgbWE8IDYam+undIJONvigAz8KR5GWblsFTEfQs0WODsjbSXWlm+JHEv8Gr6Tfdbg== dependencies: jsonparse "^1.3.1" minipass "^3.0.0" minipass-pipeline@^1.2.2, minipass-pipeline@^1.2.4: version "1.2.4" resolved "https://registry.yarnpkg.com/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz#68472f79711c084657c067c5c6ad93cddea8214c" integrity sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A== dependencies: minipass "^3.0.0" minipass-sized@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/minipass-sized/-/minipass-sized-1.0.3.tgz#70ee5a7c5052070afacfbc22977ea79def353b70" integrity sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g== dependencies: minipass "^3.0.0" minipass@^2.6.0, minipass@^2.9.0: version "2.9.0" resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.9.0.tgz#e713762e7d3e32fed803115cf93e04bca9fcc9a6" integrity sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg== dependencies: safe-buffer "^5.1.2" yallist "^3.0.0" minipass@^3.0.0, minipass@^3.1.0, minipass@^3.1.1, minipass@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.1.3.tgz#7d42ff1f39635482e15f9cdb53184deebd5815fd" integrity sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg== dependencies: yallist "^4.0.0" minizlib@^1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.3.3.tgz#2290de96818a34c29551c8a8d301216bd65a861d" integrity sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q== dependencies: minipass "^2.9.0" minizlib@^2.0.0, minizlib@^2.1.1: version "2.1.2" resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg== dependencies: minipass "^3.0.0" yallist "^4.0.0" mississippi@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/mississippi/-/mississippi-3.0.0.tgz#ea0a3291f97e0b5e8776b363d5f0a12d94c67022" integrity sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA== dependencies: concat-stream "^1.5.0" duplexify "^3.4.2" end-of-stream "^1.1.0" flush-write-stream "^1.0.0" from2 "^2.1.0" parallel-transform "^1.1.0" pump "^3.0.0" pumpify "^1.3.3" stream-each "^1.1.0" through2 "^2.0.0" mixin-deep@^1.2.0: version "1.3.2" resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566" integrity sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA== dependencies: for-in "^1.0.2" is-extendable "^1.0.1" mkdirp-infer-owner@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/mkdirp-infer-owner/-/mkdirp-infer-owner-2.0.0.tgz#55d3b368e7d89065c38f32fd38e638f0ab61d316" integrity sha512-sdqtiFt3lkOaYvTXSRIUjkIdPTcxgv5+fgqYE/5qgwdw12cOrAuzzgzvVExIkH/ul1oeHN3bCLOWSG3XOqbKKw== dependencies: chownr "^2.0.0" infer-owner "^1.0.4" mkdirp "^1.0.3" mkdirp@1.0.4, mkdirp@^1.0.3, mkdirp@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@^0.5.4, mkdirp@^0.5.5, mkdirp@~0.5.1: version "0.5.5" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== dependencies: minimist "^1.2.5" mocha@^9.0.3: version "9.0.3" resolved "https://registry.yarnpkg.com/mocha/-/mocha-9.0.3.tgz#128cd6bbd3ee0adcdaef715f357f76ec1e6227c7" integrity sha512-hnYFrSefHxYS2XFGtN01x8un0EwNu2bzKvhpRFhgoybIvMaOkkL60IVPmkb5h6XDmUl4IMSB+rT5cIO4/4bJgg== dependencies: "@ungap/promise-all-settled" "1.1.2" ansi-colors "4.1.1" browser-stdout "1.3.1" chokidar "3.5.2" debug "4.3.1" diff "5.0.0" escape-string-regexp "4.0.0" find-up "5.0.0" glob "7.1.7" growl "1.10.5" he "1.2.0" js-yaml "4.1.0" log-symbols "4.1.0" minimatch "3.0.4" ms "2.1.3" nanoid "3.1.23" serialize-javascript "6.0.0" strip-json-comments "3.1.1" supports-color "8.1.1" which "2.0.2" wide-align "1.1.3" workerpool "6.1.5" yargs "16.2.0" yargs-parser "20.2.4" yargs-unparser "2.0.0" modify-values@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/modify-values/-/modify-values-1.0.1.tgz#b3939fa605546474e3e3e3c63d64bd43b4ee6022" integrity sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw== move-concurrently@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92" integrity sha1-viwAX9oy4LKa8fBdfEszIUxwH5I= dependencies: aproba "^1.1.1" copy-concurrently "^1.0.0" fs-write-stream-atomic "^1.0.8" mkdirp "^0.5.1" rimraf "^2.5.4" run-queue "^1.0.3" ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= ms@2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== ms@2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== ms@2.1.3, ms@^2.0.0, ms@^2.1.1: version "2.1.3" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== multimatch@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/multimatch/-/multimatch-5.0.0.tgz#932b800963cea7a31a033328fa1e0c3a1874dbe6" integrity sha512-ypMKuglUrZUD99Tk2bUQ+xNQj43lPEfAeX2o9cTteAmShXy2VHDJpuwu1o0xqoKCt9jLVAvwyFKdLTPXKAfJyA== dependencies: "@types/minimatch" "^3.0.3" array-differ "^3.0.0" array-union "^2.1.0" arrify "^2.0.1" minimatch "^3.0.4" mute-stream@0.0.8, mute-stream@~0.0.4: version "0.0.8" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== mv@2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/mv/-/mv-2.1.1.tgz#ae6ce0d6f6d5e0a4f7d893798d03c1ea9559b6a2" integrity sha1-rmzg1vbV4KT32JN5jQPB6pVZtqI= dependencies: mkdirp "~0.5.1" ncp "~2.0.0" rimraf "~2.4.0" nan@^2.12.1: version "2.15.0" resolved "https://registry.yarnpkg.com/nan/-/nan-2.15.0.tgz#3f34a473ff18e15c1b5626b62903b5ad6e665fee" integrity sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ== nanoid@3.1.23: version "3.1.23" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.23.tgz#f744086ce7c2bc47ee0a8472574d5c78e4183a81" integrity sha512-FiB0kzdP0FFVGDKlRLEQ1BgDzU87dy5NnzjeW9YZNt+/c3+q82EQDUwniSAUxp/F0gFNI1ZhKU1FqYsMuqZVnw== nanomatch@^1.2.9: version "1.2.13" resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" integrity sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA== dependencies: arr-diff "^4.0.0" array-unique "^0.3.2" define-property "^2.0.2" extend-shallow "^3.0.2" fragment-cache "^0.2.1" is-windows "^1.0.2" kind-of "^6.0.2" object.pick "^1.3.0" regex-not "^1.0.0" snapdragon "^0.8.1" to-regex "^3.0.1" natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= ncp@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ncp/-/ncp-2.0.0.tgz#195a21d6c46e361d2fb1281ba38b91e9df7bdbb3" integrity sha1-GVoh1sRuNh0vsSgbo4uR6d9727M= needle@2.6.0: version "2.6.0" resolved "https://registry.yarnpkg.com/needle/-/needle-2.6.0.tgz#24dbb55f2509e2324b4a99d61f413982013ccdbe" integrity sha512-KKYdza4heMsEfSWD7VPUIz3zX2XDwOyX2d+geb4vrERZMT5RMU6ujjaD+I5Yr54uZxQ2w6XRTAhHBbSCyovZBg== dependencies: debug "^3.2.6" iconv-lite "^0.4.4" sax "^1.2.4" needle@^2.3.3, needle@^2.5.0, needle@^2.8.0: version "2.8.0" resolved "https://registry.yarnpkg.com/needle/-/needle-2.8.0.tgz#1c8ef9c1a2c29dcc1e83d73809d7bc681c80a048" integrity sha512-ZTq6WYkN/3782H1393me3utVYdq2XyqNUFBsprEE3VMAT0+hP/cItpnITpqsY6ep2yeFE4Tqtqwc74VqUlUYtw== dependencies: debug "^3.2.6" iconv-lite "^0.4.4" sax "^1.2.4" negotiator@0.6.2, negotiator@^0.6.2: version "0.6.2" resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw== neo-async@^2.5.0, neo-async@^2.6.0, neo-async@^2.6.1: version "2.6.2" resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== next-tick@1, next-tick@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.1.0.tgz#1836ee30ad56d67ef281b22bd199f709449b35eb" integrity sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ== next-tick@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c" integrity sha1-yobR/ogoFpsBICCOPchCS524NCw= nice-try@^1.0.4: version "1.0.5" resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== node-fetch@^2.6.1: version "2.6.1" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== node-gyp-build@^4.2.0: version "4.2.3" resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.2.3.tgz#ce6277f853835f718829efb47db20f3e4d9c4739" integrity sha512-MN6ZpzmfNCRM+3t57PTJHgHyw/h4OWnZ6mR8P5j/uZtqQr46RRuDE/P+g3n0YR/AiYXeWixZZzaip77gdICfRg== node-gyp@^5.0.2: version "5.1.1" resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-5.1.1.tgz#eb915f7b631c937d282e33aed44cb7a025f62a3e" integrity sha512-WH0WKGi+a4i4DUt2mHnvocex/xPLp9pYt5R6M2JdFB7pJ7Z34hveZ4nDTGTiLXCkitA9T8HFZjhinBCiVHYcWw== dependencies: env-paths "^2.2.0" glob "^7.1.4" graceful-fs "^4.2.2" mkdirp "^0.5.1" nopt "^4.0.1" npmlog "^4.1.2" request "^2.88.0" rimraf "^2.6.3" semver "^5.7.1" tar "^4.4.12" which "^1.3.1" node-gyp@^7.1.0: version "7.1.2" resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-7.1.2.tgz#21a810aebb187120251c3bcec979af1587b188ae" integrity sha512-CbpcIo7C3eMu3dL1c3d0xw449fHIGALIJsRP4DDPHpyiW8vcriNY7ubh9TE4zEKfSxscY7PjeFnshE7h75ynjQ== dependencies: env-paths "^2.2.0" glob "^7.1.4" graceful-fs "^4.2.3" nopt "^5.0.0" npmlog "^4.1.2" request "^2.88.2" rimraf "^3.0.2" semver "^7.3.2" tar "^6.0.2" which "^2.0.2" node-libs-browser@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-2.2.1.tgz#b64f513d18338625f90346d27b0d235e631f6425" integrity sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q== dependencies: assert "^1.1.1" browserify-zlib "^0.2.0" buffer "^4.3.0" console-browserify "^1.1.0" constants-browserify "^1.0.0" crypto-browserify "^3.11.0" domain-browser "^1.1.1" events "^3.0.0" https-browserify "^1.0.0" os-browserify "^0.3.0" path-browserify "0.0.1" process "^0.11.10" punycode "^1.2.4" querystring-es3 "^0.2.0" readable-stream "^2.3.3" stream-browserify "^2.0.1" stream-http "^2.7.2" string_decoder "^1.0.0" timers-browserify "^2.0.4" tty-browserify "0.0.0" url "^0.11.0" util "^0.11.0" vm-browserify "^1.0.1" node-releases@^1.1.73: version "1.1.73" resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.73.tgz#dd4e81ddd5277ff846b80b52bb40c49edf7a7b20" integrity sha512-uW7fodD6pyW2FZNZnp/Z3hvWKeEW1Y8R1+1CnErE8cXFXzl5blBOoVB41CvMer6P6Q0S5FXDwcHgFd1Wj0U9zg== node.extend@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/node.extend/-/node.extend-2.0.2.tgz#b4404525494acc99740f3703c496b7d5182cc6cc" integrity sha512-pDT4Dchl94/+kkgdwyS2PauDFjZG0Hk0IcHIB+LkW27HLDtdoeMxHTxZh39DYbPP8UflWXWj9JcdDozF+YDOpQ== dependencies: has "^1.0.3" is "^3.2.1" nopt@^4.0.1: version "4.0.3" resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.3.tgz#a375cad9d02fd921278d954c2254d5aa57e15e48" integrity sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg== dependencies: abbrev "1" osenv "^0.1.4" nopt@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/nopt/-/nopt-5.0.0.tgz#530942bb58a512fccafe53fe210f13a25355dc88" integrity sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ== dependencies: abbrev "1" normalize-package-data@^2.0.0, normalize-package-data@^2.3.2, normalize-package-data@^2.5.0, "normalize-package-data@~1.0.1 || ^2.0.0": version "2.5.0" resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== dependencies: hosted-git-info "^2.1.4" resolve "^1.10.0" semver "2 || 3 || 4 || 5" validate-npm-package-license "^3.0.1" normalize-package-data@^3.0.0, normalize-package-data@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-3.0.2.tgz#cae5c410ae2434f9a6c1baa65d5bc3b9366c8699" integrity sha512-6CdZocmfGaKnIHPVFhJJZ3GuR8SsLKvDANFp47Jmy51aKIr8akjAWTSxtpI+MBgBFdSMRyo4hMpDlT6dTffgZg== dependencies: hosted-git-info "^4.0.1" resolve "^1.20.0" semver "^7.3.4" validate-npm-package-license "^3.0.1" normalize-path@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" integrity sha1-GrKLVW4Zg2Oowab35vogE3/mrtk= dependencies: remove-trailing-separator "^1.0.1" normalize-path@^3.0.0, normalize-path@~3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== normalize-url@^3.0.0: version "3.3.0" resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-3.3.0.tgz#b2e1c4dc4f7c6d57743df733a4f5978d18650559" integrity sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg== normalize-url@^4.1.0: version "4.5.1" resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.1.tgz#0dd90cf1288ee1d1313b87081c9a5932ee48518a" integrity sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA== normalize-url@^6.0.1, normalize-url@^6.1.0: version "6.1.0" resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a" integrity sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A== npm-bundled@^1.1.1: version "1.1.2" resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.1.2.tgz#944c78789bd739035b70baa2ca5cc32b8d860bc1" integrity sha512-x5DHup0SuyQcmL3s7Rx/YQ8sbw/Hzg0rj48eN0dV7hf5cmQq5PXIeioroH3raV1QC1yh3uTYuMThvEQF3iKgGQ== dependencies: npm-normalize-package-bin "^1.0.1" npm-cli-login@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/npm-cli-login/-/npm-cli-login-0.1.1.tgz#12e5bebc753cf433a3edab75be1d76f696c6fb86" integrity sha512-IWEsRe/f6VWcKWPuQYHNEyKF5SPLjxS5Lyn2W4/Gxx4lxZLzo2HKIJgibVAH/rVN80mi7r75ahKUmSNMFDulGQ== dependencies: npm-registry-client "8.6.0" snyk "^1.91.0" npm-install-checks@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/npm-install-checks/-/npm-install-checks-4.0.0.tgz#a37facc763a2fde0497ef2c6d0ac7c3fbe00d7b4" integrity sha512-09OmyDkNLYwqKPOnbI8exiOZU2GVVmQp7tgez2BPi5OZC8M82elDAps7sxC4l//uSUtotWqoEIDwjRvWH4qz8w== dependencies: semver "^7.1.1" npm-lifecycle@^3.1.5: version "3.1.5" resolved "https://registry.yarnpkg.com/npm-lifecycle/-/npm-lifecycle-3.1.5.tgz#9882d3642b8c82c815782a12e6a1bfeed0026309" integrity sha512-lDLVkjfZmvmfvpvBzA4vzee9cn+Me4orq0QF8glbswJVEbIcSNWib7qGOffolysc3teCqbbPZZkzbr3GQZTL1g== dependencies: byline "^5.0.0" graceful-fs "^4.1.15" node-gyp "^5.0.2" resolve-from "^4.0.0" slide "^1.1.6" uid-number "0.0.6" umask "^1.1.0" which "^1.3.1" npm-normalize-package-bin@^1.0.0, npm-normalize-package-bin@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz#6e79a41f23fd235c0623218228da7d9c23b8f6e2" integrity sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA== "npm-package-arg@^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0": version "6.1.1" resolved "https://registry.yarnpkg.com/npm-package-arg/-/npm-package-arg-6.1.1.tgz#02168cb0a49a2b75bf988a28698de7b529df5cb7" integrity sha512-qBpssaL3IOZWi5vEKUKW0cO7kzLeT+EQO9W8RsLOZf76KF9E/K9+wH0C7t06HXPpaH8WH5xF1MExLuCwbTqRUg== dependencies: hosted-git-info "^2.7.1" osenv "^0.1.5" semver "^5.6.0" validate-npm-package-name "^3.0.0" npm-package-arg@^8.0.0, npm-package-arg@^8.0.1, npm-package-arg@^8.1.0, npm-package-arg@^8.1.2: version "8.1.5" resolved "https://registry.yarnpkg.com/npm-package-arg/-/npm-package-arg-8.1.5.tgz#3369b2d5fe8fdc674baa7f1786514ddc15466e44" integrity sha512-LhgZrg0n0VgvzVdSm1oiZworPbTxYHUJCgtsJW8mGvlDpxTM1vSJc3m5QZeUkhAHIzbz3VCHd/R4osi1L1Tg/Q== dependencies: hosted-git-info "^4.0.1" semver "^7.3.4" validate-npm-package-name "^3.0.0" npm-packlist@^2.1.4: version "2.2.2" resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-2.2.2.tgz#076b97293fa620f632833186a7a8f65aaa6148c8" integrity sha512-Jt01acDvJRhJGthnUJVF/w6gumWOZxO7IkpY/lsX9//zqQgnF7OJaxgQXcerd4uQOLu7W5bkb4mChL9mdfm+Zg== dependencies: glob "^7.1.6" ignore-walk "^3.0.3" npm-bundled "^1.1.1" npm-normalize-package-bin "^1.0.1" npm-pick-manifest@^6.0.0, npm-pick-manifest@^6.1.1: version "6.1.1" resolved "https://registry.yarnpkg.com/npm-pick-manifest/-/npm-pick-manifest-6.1.1.tgz#7b5484ca2c908565f43b7f27644f36bb816f5148" integrity sha512-dBsdBtORT84S8V8UTad1WlUyKIY9iMsAmqxHbLdeEeBNMLQDlDWWra3wYUx9EBEIiG/YwAy0XyNHDd2goAsfuA== dependencies: npm-install-checks "^4.0.0" npm-normalize-package-bin "^1.0.1" npm-package-arg "^8.1.2" semver "^7.3.4" npm-registry-client@8.6.0: version "8.6.0" resolved "https://registry.yarnpkg.com/npm-registry-client/-/npm-registry-client-8.6.0.tgz#7f1529f91450732e89f8518e0f21459deea3e4c4" integrity sha512-Qs6P6nnopig+Y8gbzpeN/dkt+n7IyVd8f45NTMotGk6Qo7GfBmzwYx6jRLoOOgKiMnaQfYxsuyQlD8Mc3guBhg== dependencies: concat-stream "^1.5.2" graceful-fs "^4.1.6" normalize-package-data "~1.0.1 || ^2.0.0" npm-package-arg "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0" once "^1.3.3" request "^2.74.0" retry "^0.10.0" safe-buffer "^5.1.1" semver "2 >=2.2.1 || 3.x || 4 || 5" slide "^1.1.3" ssri "^5.2.4" optionalDependencies: npmlog "2 || ^3.1.0 || ^4.0.0" npm-registry-fetch@^11.0.0: version "11.0.0" resolved "https://registry.yarnpkg.com/npm-registry-fetch/-/npm-registry-fetch-11.0.0.tgz#68c1bb810c46542760d62a6a965f85a702d43a76" integrity sha512-jmlgSxoDNuhAtxUIG6pVwwtz840i994dL14FoNVZisrmZW5kWd63IUTNv1m/hyRSGSqWjCUp/YZlS1BJyNp9XA== dependencies: make-fetch-happen "^9.0.1" minipass "^3.1.3" minipass-fetch "^1.3.0" minipass-json-stream "^1.0.1" minizlib "^2.0.0" npm-package-arg "^8.0.0" npm-registry-fetch@^9.0.0: version "9.0.0" resolved "https://registry.yarnpkg.com/npm-registry-fetch/-/npm-registry-fetch-9.0.0.tgz#86f3feb4ce00313bc0b8f1f8f69daae6face1661" integrity sha512-PuFYYtnQ8IyVl6ib9d3PepeehcUeHN9IO5N/iCRhyg9tStQcqGQBRVHmfmMWPDERU3KwZoHFvbJ4FPXPspvzbA== dependencies: "@npmcli/ci-detect" "^1.0.0" lru-cache "^6.0.0" make-fetch-happen "^8.0.9" minipass "^3.1.3" minipass-fetch "^1.3.0" minipass-json-stream "^1.0.1" minizlib "^2.0.0" npm-package-arg "^8.0.0" npm-run-path@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" integrity sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8= dependencies: path-key "^2.0.0" npm-run-path@^4.0.0, npm-run-path@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== dependencies: path-key "^3.0.0" "npmlog@2 || ^3.1.0 || ^4.0.0", npmlog@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg== dependencies: are-we-there-yet "~1.1.2" console-control-strings "~1.1.0" gauge "~2.7.3" set-blocking "~2.0.0" nth-check@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-1.0.2.tgz#b2bd295c37e3dd58a3bf0700376663ba4d9cf05c" integrity sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg== dependencies: boolbase "~1.0.0" number-is-nan@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= nwsapi@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.0.tgz#204879a9e3d068ff2a55139c2c772780681a38b7" integrity sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ== oauth-sign@~0.9.0: version "0.9.0" resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== object-assign@^4, object-assign@^4.1.0, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= object-copy@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c" integrity sha1-fn2Fi3gb18mRpBupde04EnVOmYw= dependencies: copy-descriptor "^0.1.0" define-property "^0.2.5" kind-of "^3.0.3" object-hash@^2.0.3: version "2.2.0" resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-2.2.0.tgz#5ad518581eefc443bd763472b8ff2e9c2c0d54a5" integrity sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw== object-inspect@^1.11.0, object-inspect@^1.9.0: version "1.11.0" resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.11.0.tgz#9dceb146cedd4148a0d9e51ab88d34cf509922b1" integrity sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg== object-keys@^1.0.12, object-keys@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== object-visit@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb" integrity sha1-95xEk68MU3e1n+OdOV5BBC3QRbs= dependencies: isobject "^3.0.0" object.assign@^4.1.0, object.assign@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940" integrity sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ== dependencies: call-bind "^1.0.0" define-properties "^1.1.3" has-symbols "^1.0.1" object-keys "^1.1.1" object.getownpropertydescriptors@^2.0.3, object.getownpropertydescriptors@^2.1.0: version "2.1.2" resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.2.tgz#1bd63aeacf0d5d2d2f31b5e393b03a7c601a23f7" integrity sha512-WtxeKSzfBjlzL+F9b7M7hewDzMwy+C8NRssHd1YrNlzHzIDrXcXiNOMrezdAEM4UXixgV+vvnyBeN7Rygl2ttQ== dependencies: call-bind "^1.0.2" define-properties "^1.1.3" es-abstract "^1.18.0-next.2" object.pick@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" integrity sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c= dependencies: isobject "^3.0.1" object.values@^1.1.0: version "1.1.4" resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.4.tgz#0d273762833e816b693a637d30073e7051535b30" integrity sha512-TnGo7j4XSnKQoK3MfvkzqKCi0nVe/D9I9IjwTNYdb/fxYHpjrluHVOgw0AF6jrRFGMPHdfuidR09tIDiIvnaSg== dependencies: call-bind "^1.0.2" define-properties "^1.1.3" es-abstract "^1.18.2" on-finished@~2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc= dependencies: ee-first "1.1.1" on-headers@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f" integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA== once@^1.3.0, once@^1.3.1, once@^1.3.3, once@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= dependencies: wrappy "1" once@~1.3.0: version "1.3.3" resolved "https://registry.yarnpkg.com/once/-/once-1.3.3.tgz#b2e261557ce4c314ec8304f3fa82663e4297ca20" integrity sha1-suJhVXzkwxTsgwTz+oJmPkKXyiA= dependencies: wrappy "1" onetime@^5.1.0, onetime@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== dependencies: mimic-fn "^2.1.0" open@^7.0.3: version "7.4.2" resolved "https://registry.yarnpkg.com/open/-/open-7.4.2.tgz#b8147e26dcf3e426316c730089fd71edd29c2321" integrity sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q== dependencies: is-docker "^2.0.0" is-wsl "^2.1.1" opencollective-postinstall@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz#7a0fff978f6dbfa4d006238fbac98ed4198c3259" integrity sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q== optionator@^0.8.1: version "0.8.3" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA== dependencies: deep-is "~0.1.3" fast-levenshtein "~2.0.6" levn "~0.3.0" prelude-ls "~1.1.2" type-check "~0.3.2" word-wrap "~1.2.3" optionator@^0.9.1: version "0.9.1" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499" integrity sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw== dependencies: deep-is "^0.1.3" fast-levenshtein "^2.0.6" levn "^0.4.1" prelude-ls "^1.2.1" type-check "^0.4.0" word-wrap "^1.2.3" ora@5.4.0: version "5.4.0" resolved "https://registry.yarnpkg.com/ora/-/ora-5.4.0.tgz#42eda4855835b9cd14d33864c97a3c95a3f56bf4" integrity sha512-1StwyXQGoU6gdjYkyVcqOLnVlbKj+6yPNNOxJVgpt9t4eksKjiriiHuxktLYkgllwk+D6MbC4ihH84L1udRXPg== dependencies: bl "^4.1.0" chalk "^4.1.0" cli-cursor "^3.1.0" cli-spinners "^2.5.0" is-interactive "^1.0.0" is-unicode-supported "^0.1.0" log-symbols "^4.1.0" strip-ansi "^6.0.0" wcwidth "^1.0.1" os-browserify@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27" integrity sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc= os-homedir@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M= os-name@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/os-name/-/os-name-3.1.0.tgz#dec19d966296e1cd62d701a5a66ee1ddeae70801" integrity sha512-h8L+8aNjNcMpo/mAIBPn5PXCM16iyPGjHNWo6U1YO8sJTMHtEtyczI6QJnLoplswm6goopQkqc7OAnjhWcugVg== dependencies: macos-release "^2.2.0" windows-release "^3.1.0" os-tmpdir@^1.0.0, os-tmpdir@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= os@~0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/os/-/os-0.1.2.tgz#f29a50c62908516ba42652de42f7038600cadbc2" integrity sha512-ZoXJkvAnljwvc56MbvhtKVWmSkzV712k42Is2mA0+0KTSRakq5XXuXpjZjgAt9ctzl51ojhQWakQQpmOvXWfjQ== osenv@^0.1.4, osenv@^0.1.5: version "0.1.5" resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410" integrity sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g== dependencies: os-homedir "^1.0.0" os-tmpdir "^1.0.0" p-cancelable@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-1.1.0.tgz#d078d15a3af409220c886f1d9a0ca2e441ab26cc" integrity sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw== p-cancelable@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-2.1.1.tgz#aab7fbd416582fa32a3db49859c122487c5ed2cf" integrity sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg== p-finally@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4= p-limit@^1.1.0: version "1.3.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8" integrity sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q== dependencies: p-try "^1.0.0" p-limit@^2.0.0, p-limit@^2.2.0: version "2.3.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== dependencies: p-try "^2.0.0" p-limit@^3.0.2: version "3.1.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== dependencies: yocto-queue "^0.1.0" p-locate@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" integrity sha1-IKAQOyIqcMj9OcwuWAaA893l7EM= dependencies: p-limit "^1.1.0" p-locate@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4" integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ== dependencies: p-limit "^2.0.0" p-locate@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== dependencies: p-limit "^2.2.0" p-locate@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== dependencies: p-limit "^3.0.2" p-map-series@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/p-map-series/-/p-map-series-2.1.0.tgz#7560d4c452d9da0c07e692fdbfe6e2c81a2a91f2" integrity sha512-RpYIIK1zXSNEOdwxcfe7FdvGcs7+y5n8rifMhMNWvaxRNMPINJHF5GDeuVxWqnfrcHPSCnp7Oo5yNXHId9Av2Q== p-map@2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/p-map/-/p-map-2.1.0.tgz#310928feef9c9ecc65b68b17693018a665cea175" integrity sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw== p-map@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b" integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ== dependencies: aggregate-error "^3.0.0" p-pipe@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/p-pipe/-/p-pipe-3.1.0.tgz#48b57c922aa2e1af6a6404cb7c6bf0eb9cc8e60e" integrity sha512-08pj8ATpzMR0Y80x50yJHn37NF6vjrqHutASaX5LiH5npS9XPvrUmscd9MF5R4fuYRHOxQR1FfMIlF7AzwoPqw== p-queue@^6.6.2: version "6.6.2" resolved "https://registry.yarnpkg.com/p-queue/-/p-queue-6.6.2.tgz#2068a9dcf8e67dd0ec3e7a2bcb76810faa85e426" integrity sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ== dependencies: eventemitter3 "^4.0.4" p-timeout "^3.2.0" p-reduce@^2.0.0, p-reduce@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/p-reduce/-/p-reduce-2.1.0.tgz#09408da49507c6c274faa31f28df334bc712b64a" integrity sha512-2USApvnsutq8uoxZBGbbWM0JIYLiEMJ9RlaN7fAzVNb9OZN0SHjjTTfIcb667XynS5Y1VhwDJVDa72TnPzAYWw== p-timeout@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-3.2.0.tgz#c7e17abc971d2a7962ef83626b35d635acf23dfe" integrity sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg== dependencies: p-finally "^1.0.0" p-try@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" integrity sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M= p-try@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== p-waterfall@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/p-waterfall/-/p-waterfall-2.1.1.tgz#63153a774f472ccdc4eb281cdb2967fcf158b2ee" integrity sha512-RRTnDb2TBG/epPRI2yYXsimO0v3BXC8Yd3ogr1545IaqKK17VGhbWVeGGN+XfCm/08OK8635nH31c8bATkHuSw== dependencies: p-reduce "^2.0.0" package-json@^6.3.0, package-json@^6.5.0: version "6.5.0" resolved "https://registry.yarnpkg.com/package-json/-/package-json-6.5.0.tgz#6feedaca35e75725876d0b0e64974697fed145b0" integrity sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ== dependencies: got "^9.6.0" registry-auth-token "^4.0.0" registry-url "^5.0.0" semver "^6.2.0" pacote@^11.2.6: version "11.3.5" resolved "https://registry.yarnpkg.com/pacote/-/pacote-11.3.5.tgz#73cf1fc3772b533f575e39efa96c50be8c3dc9d2" integrity sha512-fT375Yczn4zi+6Hkk2TBe1x1sP8FgFsEIZ2/iWaXY2r/NkhDJfxbcn5paz1+RTFCyNf+dPnaoBDJoAxXSU8Bkg== dependencies: "@npmcli/git" "^2.1.0" "@npmcli/installed-package-contents" "^1.0.6" "@npmcli/promise-spawn" "^1.2.0" "@npmcli/run-script" "^1.8.2" cacache "^15.0.5" chownr "^2.0.0" fs-minipass "^2.1.0" infer-owner "^1.0.4" minipass "^3.1.3" mkdirp "^1.0.3" npm-package-arg "^8.0.1" npm-packlist "^2.1.4" npm-pick-manifest "^6.0.0" npm-registry-fetch "^11.0.0" promise-retry "^2.0.1" read-package-json-fast "^2.0.1" rimraf "^3.0.2" ssri "^8.0.1" tar "^6.1.0" pako@~0.2.0: version "0.2.9" resolved "https://registry.yarnpkg.com/pako/-/pako-0.2.9.tgz#f3f7522f4ef782348da8161bad9ecfd51bf83a75" integrity sha1-8/dSL073gjSNqBYbrZ7P1Rv4OnU= pako@~1.0.2, pako@~1.0.5: version "1.0.11" resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== parallel-transform@^1.1.0: version "1.2.0" resolved "https://registry.yarnpkg.com/parallel-transform/-/parallel-transform-1.2.0.tgz#9049ca37d6cb2182c3b1d2c720be94d14a5814fc" integrity sha512-P2vSmIu38uIlvdcU7fDkyrxj33gTUy/ABO5ZUbGowxNCopBq/OoD42bP4UmMrJoPyk4Uqf0mu3mtWBhHCZD8yg== dependencies: cyclist "^1.0.1" inherits "^2.0.3" readable-stream "^2.1.5" parent-module@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== dependencies: callsites "^3.0.0" parse-asn1@^5.0.0, parse-asn1@^5.1.5: version "5.1.6" resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.6.tgz#385080a3ec13cb62a62d39409cb3e88844cdaed4" integrity sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw== dependencies: asn1.js "^5.2.0" browserify-aes "^1.0.0" evp_bytestokey "^1.0.0" pbkdf2 "^3.0.3" safe-buffer "^5.1.1" parse-json@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0" integrity sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA= dependencies: error-ex "^1.3.1" json-parse-better-errors "^1.0.1" parse-json@^5.0.0: version "5.2.0" resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== dependencies: "@babel/code-frame" "^7.0.0" error-ex "^1.3.1" json-parse-even-better-errors "^2.3.0" lines-and-columns "^1.1.6" parse-link-header@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/parse-link-header/-/parse-link-header-1.0.1.tgz#bedfe0d2118aeb84be75e7b025419ec8a61140a7" integrity sha1-vt/g0hGK64S+deewJUGeyKYRQKc= dependencies: xtend "~4.0.1" parse-ms@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/parse-ms/-/parse-ms-2.1.0.tgz#348565a753d4391fa524029956b172cb7753097d" integrity sha512-kHt7kzLoS9VBZfUsiKjv43mr91ea+U05EyKkEtqp7vNbHxmaVuEqN7XxeEVnGrMtYOAxGrDElSi96K7EgO1zCA== parse-passwd@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6" integrity sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY= parse-path@^4.0.0: version "4.0.3" resolved "https://registry.yarnpkg.com/parse-path/-/parse-path-4.0.3.tgz#82d81ec3e071dcc4ab49aa9f2c9c0b8966bb22bf" integrity sha512-9Cepbp2asKnWTJ9x2kpw6Fe8y9JDbqwahGCTvklzd/cEq5C5JC59x2Xb0Kx+x0QZ8bvNquGO8/BWP0cwBHzSAA== dependencies: is-ssh "^1.3.0" protocols "^1.4.0" qs "^6.9.4" query-string "^6.13.8" parse-url@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/parse-url/-/parse-url-6.0.0.tgz#f5dd262a7de9ec00914939220410b66cff09107d" integrity sha512-cYyojeX7yIIwuJzledIHeLUBVJ6COVLeT4eF+2P6aKVzwvgKQPndCBv3+yQ7pcWjqToYwaligxzSYNNmGoMAvw== dependencies: is-ssh "^1.3.0" normalize-url "^6.1.0" parse-path "^4.0.0" protocols "^1.4.0" parse5@5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/parse5/-/parse5-5.1.0.tgz#c59341c9723f414c452975564c7c00a68d58acd2" integrity sha512-fxNG2sQjHvlVAYmzBZS9YlDp6PTSSDwa98vkD4QgVDDCAo84z5X1t5XyJQ62ImdLXx5NdIIfihey6xpum9/gRQ== parseurl@~1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== pascalcase@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ= path-browserify@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.1.tgz#e6c4ddd7ed3aa27c68a20cc4e50e1a4ee83bbc4a" integrity sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ== path-dirname@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0" integrity sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA= path-exists@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU= path-exists@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== path-is-absolute@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= path-key@^2.0.0, path-key@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= path-key@^3.0.0, path-key@^3.1.0: version "3.1.1" resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== path-parse@^1.0.6: version "1.0.7" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== path-to-regexp@0.1.7: version "0.1.7" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= path-type@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-3.0.0.tgz#cef31dc8e0a1a3bb0d105c0cd97cf3bf47f4e36f" integrity sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg== dependencies: pify "^3.0.0" path-type@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== pathval@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.1.tgz#8534e77a77ce7ac5a2512ea21e0fdb8fcf6c3d8d" integrity sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ== pbkdf2@^3.0.3: version "3.1.2" resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.1.2.tgz#dd822aa0887580e52f1a039dc3eda108efae3075" integrity sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA== dependencies: create-hash "^1.1.2" create-hmac "^1.1.4" ripemd160 "^2.0.1" safe-buffer "^5.0.1" sha.js "^2.4.8" peek-stream@^1.1.0: version "1.1.3" resolved "https://registry.yarnpkg.com/peek-stream/-/peek-stream-1.1.3.tgz#3b35d84b7ccbbd262fff31dc10da56856ead6d67" integrity sha512-FhJ+YbOSBb9/rIl2ZeE/QHEsWn7PqNYt8ARAY3kIgNGOk13g9FGyIY6JIl/xB/3TFRVoTv5as0l11weORrTekA== dependencies: buffer-from "^1.0.0" duplexify "^3.5.0" through2 "^2.0.3" pegjs@^0.10.0: version "0.10.0" resolved "https://registry.yarnpkg.com/pegjs/-/pegjs-0.10.0.tgz#cf8bafae6eddff4b5a7efb185269eaaf4610ddbd" integrity sha1-z4uvrm7d/0tafvsYUmnqr0YQ3b0= pend@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" integrity sha1-elfrVQpng/kRUzH89GY9XI4AelA= performance-now@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= picomatch@^2.0.4, picomatch@^2.0.5, picomatch@^2.2.1, picomatch@^2.2.2, picomatch@^2.2.3: version "2.3.0" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972" integrity sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw== pify@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" integrity sha1-7RQaasBDqEnqWISY59yosVMw6Qw= pify@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY= pify@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== pify@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/pify/-/pify-5.0.0.tgz#1f5eca3f5e87ebec28cc6d54a0e4aaf00acc127f" integrity sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA== pino-std-serializers@^3.1.0: version "3.2.0" resolved "https://registry.yarnpkg.com/pino-std-serializers/-/pino-std-serializers-3.2.0.tgz#b56487c402d882eb96cd67c257868016b61ad671" integrity sha512-EqX4pwDPrt3MuOAAUBMU0Tk5kR/YcCM5fNPEzgCO2zJ5HfX0vbiH9HbJglnyeQsN96Kznae6MWD47pZB5avTrg== pino@6.12.0: version "6.12.0" resolved "https://registry.yarnpkg.com/pino/-/pino-6.12.0.tgz#2281521620d70eeff519039467352d656f46735e" integrity sha512-5NGopOcUusGuklGHVVv9az0Hv/Dj3urHhD3G+zhl5pBGIRYAeGCi/Ej6YCl16Q2ko28cmYiJz+/qRoJiwy62Rw== dependencies: fast-redact "^3.0.0" fast-safe-stringify "^2.0.8" flatstr "^1.0.12" pino-std-serializers "^3.1.0" quick-format-unescaped "^4.0.3" sonic-boom "^1.0.2" pkg-dir@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-3.0.0.tgz#2749020f239ed990881b1f71210d51eb6523bea3" integrity sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw== dependencies: find-up "^3.0.0" pkg-dir@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== dependencies: find-up "^4.0.0" pkg-dir@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-5.0.0.tgz#a02d6aebe6ba133a928f74aec20bafdfe6b8e760" integrity sha512-NPE8TDbzl/3YQYY7CSS228s3g2ollTFnc+Qi3tqmqJp9Vg2ovUpixcJEo2HJScN2Ez+kEaal6y70c0ehqJBJeA== dependencies: find-up "^5.0.0" pkginfo@0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/pkginfo/-/pkginfo-0.4.1.tgz#b5418ef0439de5425fc4995042dced14fb2a84ff" integrity sha1-tUGO8EOd5UJfxJlQQtztFPsqhP8= please-upgrade-node@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz#aeddd3f994c933e4ad98b99d9a556efa0e2fe942" integrity sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg== dependencies: semver-compare "^1.0.0" pluralize@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-7.0.0.tgz#298b89df8b93b0221dbf421ad2b1b1ea23fc6777" integrity sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow== pn@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/pn/-/pn-1.1.0.tgz#e2f4cef0e219f463c179ab37463e4e1ecdccbafb" integrity sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA== posix-character-classes@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs= postcss-calc@^7.0.1: version "7.0.5" resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-7.0.5.tgz#f8a6e99f12e619c2ebc23cf6c486fdc15860933e" integrity sha512-1tKHutbGtLtEZF6PT4JSihCHfIVldU72mZ8SdZHIYriIZ9fh9k9aWSppaT8rHsyI3dX+KSR+W+Ix9BMY3AODrg== dependencies: postcss "^7.0.27" postcss-selector-parser "^6.0.2" postcss-value-parser "^4.0.2" postcss-colormin@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-4.0.3.tgz#ae060bce93ed794ac71264f08132d550956bd381" integrity sha512-WyQFAdDZpExQh32j0U0feWisZ0dmOtPl44qYmJKkq9xFWY3p+4qnRzCHeNrkeRhwPHz9bQ3mo0/yVkaply0MNw== dependencies: browserslist "^4.0.0" color "^3.0.0" has "^1.0.0" postcss "^7.0.0" postcss-value-parser "^3.0.0" postcss-convert-values@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/postcss-convert-values/-/postcss-convert-values-4.0.1.tgz#ca3813ed4da0f812f9d43703584e449ebe189a7f" integrity sha512-Kisdo1y77KUC0Jmn0OXU/COOJbzM8cImvw1ZFsBgBgMgb1iL23Zs/LXRe3r+EZqM3vGYKdQ2YJVQ5VkJI+zEJQ== dependencies: postcss "^7.0.0" postcss-value-parser "^3.0.0" postcss-discard-comments@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/postcss-discard-comments/-/postcss-discard-comments-4.0.2.tgz#1fbabd2c246bff6aaad7997b2b0918f4d7af4033" integrity sha512-RJutN259iuRf3IW7GZyLM5Sw4GLTOH8FmsXBnv8Ab/Tc2k4SR4qbV4DNbyyY4+Sjo362SyDmW2DQ7lBSChrpkg== dependencies: postcss "^7.0.0" postcss-discard-duplicates@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/postcss-discard-duplicates/-/postcss-discard-duplicates-4.0.2.tgz#3fe133cd3c82282e550fc9b239176a9207b784eb" integrity sha512-ZNQfR1gPNAiXZhgENFfEglF93pciw0WxMkJeVmw8eF+JZBbMD7jp6C67GqJAXVZP2BWbOztKfbsdmMp/k8c6oQ== dependencies: postcss "^7.0.0" postcss-discard-empty@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/postcss-discard-empty/-/postcss-discard-empty-4.0.1.tgz#c8c951e9f73ed9428019458444a02ad90bb9f765" integrity sha512-B9miTzbznhDjTfjvipfHoqbWKwd0Mj+/fL5s1QOz06wufguil+Xheo4XpOnc4NqKYBCNqqEzgPv2aPBIJLox0w== dependencies: postcss "^7.0.0" postcss-discard-overridden@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/postcss-discard-overridden/-/postcss-discard-overridden-4.0.1.tgz#652aef8a96726f029f5e3e00146ee7a4e755ff57" integrity sha512-IYY2bEDD7g1XM1IDEsUT4//iEYCxAmP5oDSFMVU/JVvT7gh+l4fmjciLqGgwjdWpQIdb0Che2VX00QObS5+cTg== dependencies: postcss "^7.0.0" postcss-load-config@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-3.1.0.tgz#d39c47091c4aec37f50272373a6a648ef5e97829" integrity sha512-ipM8Ds01ZUophjDTQYSVP70slFSYg3T0/zyfII5vzhN6V57YSxMgG5syXuwi5VtS8wSf3iL30v0uBdoIVx4Q0g== dependencies: import-cwd "^3.0.0" lilconfig "^2.0.3" yaml "^1.10.2" postcss-merge-longhand@^4.0.11: version "4.0.11" resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-4.0.11.tgz#62f49a13e4a0ee04e7b98f42bb16062ca2549e24" integrity sha512-alx/zmoeXvJjp7L4mxEMjh8lxVlDFX1gqWHzaaQewwMZiVhLo42TEClKaeHbRf6J7j82ZOdTJ808RtN0ZOZwvw== dependencies: css-color-names "0.0.4" postcss "^7.0.0" postcss-value-parser "^3.0.0" stylehacks "^4.0.0" postcss-merge-rules@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/postcss-merge-rules/-/postcss-merge-rules-4.0.3.tgz#362bea4ff5a1f98e4075a713c6cb25aefef9a650" integrity sha512-U7e3r1SbvYzO0Jr3UT/zKBVgYYyhAz0aitvGIYOYK5CPmkNih+WDSsS5tvPrJ8YMQYlEMvsZIiqmn7HdFUaeEQ== dependencies: browserslist "^4.0.0" caniuse-api "^3.0.0" cssnano-util-same-parent "^4.0.0" postcss "^7.0.0" postcss-selector-parser "^3.0.0" vendors "^1.0.0" postcss-minify-font-values@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/postcss-minify-font-values/-/postcss-minify-font-values-4.0.2.tgz#cd4c344cce474343fac5d82206ab2cbcb8afd5a6" integrity sha512-j85oO6OnRU9zPf04+PZv1LYIYOprWm6IA6zkXkrJXyRveDEuQggG6tvoy8ir8ZwjLxLuGfNkCZEQG7zan+Hbtg== dependencies: postcss "^7.0.0" postcss-value-parser "^3.0.0" postcss-minify-gradients@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/postcss-minify-gradients/-/postcss-minify-gradients-4.0.2.tgz#93b29c2ff5099c535eecda56c4aa6e665a663471" integrity sha512-qKPfwlONdcf/AndP1U8SJ/uzIJtowHlMaSioKzebAXSG4iJthlWC9iSWznQcX4f66gIWX44RSA841HTHj3wK+Q== dependencies: cssnano-util-get-arguments "^4.0.0" is-color-stop "^1.0.0" postcss "^7.0.0" postcss-value-parser "^3.0.0" postcss-minify-params@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/postcss-minify-params/-/postcss-minify-params-4.0.2.tgz#6b9cef030c11e35261f95f618c90036d680db874" integrity sha512-G7eWyzEx0xL4/wiBBJxJOz48zAKV2WG3iZOqVhPet/9geefm/Px5uo1fzlHu+DOjT+m0Mmiz3jkQzVHe6wxAWg== dependencies: alphanum-sort "^1.0.0" browserslist "^4.0.0" cssnano-util-get-arguments "^4.0.0" postcss "^7.0.0" postcss-value-parser "^3.0.0" uniqs "^2.0.0" postcss-minify-selectors@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/postcss-minify-selectors/-/postcss-minify-selectors-4.0.2.tgz#e2e5eb40bfee500d0cd9243500f5f8ea4262fbd8" integrity sha512-D5S1iViljXBj9kflQo4YutWnJmwm8VvIsU1GeXJGiG9j8CIg9zs4voPMdQDUmIxetUOh60VilsNzCiAFTOqu3g== dependencies: alphanum-sort "^1.0.0" has "^1.0.0" postcss "^7.0.0" postcss-selector-parser "^3.0.0" postcss-modules-extract-imports@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-2.0.0.tgz#818719a1ae1da325f9832446b01136eeb493cd7e" integrity sha512-LaYLDNS4SG8Q5WAWqIJgdHPJrDDr/Lv775rMBFUbgjTz6j34lUznACHcdRWroPvXANP2Vj7yNK57vp9eFqzLWQ== dependencies: postcss "^7.0.5" postcss-modules-extract-imports@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz#cda1f047c0ae80c97dbe28c3e76a43b88025741d" integrity sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw== postcss-modules-local-by-default@^3.0.2: version "3.0.3" resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-3.0.3.tgz#bb14e0cc78279d504dbdcbfd7e0ca28993ffbbb0" integrity sha512-e3xDq+LotiGesympRlKNgaJ0PCzoUIdpH0dj47iWAui/kyTgh3CiAr1qP54uodmJhl6p9rN6BoNcdEDVJx9RDw== dependencies: icss-utils "^4.1.1" postcss "^7.0.32" postcss-selector-parser "^6.0.2" postcss-value-parser "^4.1.0" postcss-modules-local-by-default@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz#ebbb54fae1598eecfdf691a02b3ff3b390a5a51c" integrity sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ== dependencies: icss-utils "^5.0.0" postcss-selector-parser "^6.0.2" postcss-value-parser "^4.1.0" postcss-modules-scope@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-2.2.0.tgz#385cae013cc7743f5a7d7602d1073a89eaae62ee" integrity sha512-YyEgsTMRpNd+HmyC7H/mh3y+MeFWevy7V1evVhJWewmMbjDHIbZbOXICC2y+m1xI1UVfIT1HMW/O04Hxyu9oXQ== dependencies: postcss "^7.0.6" postcss-selector-parser "^6.0.0" postcss-modules-scope@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz#9ef3151456d3bbfa120ca44898dfca6f2fa01f06" integrity sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg== dependencies: postcss-selector-parser "^6.0.4" postcss-modules-values@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-3.0.0.tgz#5b5000d6ebae29b4255301b4a3a54574423e7f10" integrity sha512-1//E5jCBrZ9DmRX+zCtmQtRSV6PV42Ix7Bzj9GbwJceduuf7IqP8MgeTXuRDHOWj2m0VzZD5+roFWDuU8RQjcg== dependencies: icss-utils "^4.0.0" postcss "^7.0.6" postcss-modules-values@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz#d7c5e7e68c3bb3c9b27cbf48ca0bb3ffb4602c9c" integrity sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ== dependencies: icss-utils "^5.0.0" postcss-modules@^4.0.0: version "4.2.2" resolved "https://registry.yarnpkg.com/postcss-modules/-/postcss-modules-4.2.2.tgz#5e7777c5a8964ea176919d90b2e54ef891321ce5" integrity sha512-/H08MGEmaalv/OU8j6bUKi/kZr2kqGF6huAW8m9UAgOLWtpFdhA14+gPBoymtqyv+D4MLsmqaF2zvIegdCxJXg== dependencies: generic-names "^2.0.1" icss-replace-symbols "^1.1.0" lodash.camelcase "^4.3.0" postcss-modules-extract-imports "^3.0.0" postcss-modules-local-by-default "^4.0.0" postcss-modules-scope "^3.0.0" postcss-modules-values "^4.0.0" string-hash "^1.1.1" postcss-normalize-charset@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/postcss-normalize-charset/-/postcss-normalize-charset-4.0.1.tgz#8b35add3aee83a136b0471e0d59be58a50285dd4" integrity sha512-gMXCrrlWh6G27U0hF3vNvR3w8I1s2wOBILvA87iNXaPvSNo5uZAMYsZG7XjCUf1eVxuPfyL4TJ7++SGZLc9A3g== dependencies: postcss "^7.0.0" postcss-normalize-display-values@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/postcss-normalize-display-values/-/postcss-normalize-display-values-4.0.2.tgz#0dbe04a4ce9063d4667ed2be476bb830c825935a" integrity sha512-3F2jcsaMW7+VtRMAqf/3m4cPFhPD3EFRgNs18u+k3lTJJlVe7d0YPO+bnwqo2xg8YiRpDXJI2u8A0wqJxMsQuQ== dependencies: cssnano-util-get-match "^4.0.0" postcss "^7.0.0" postcss-value-parser "^3.0.0" postcss-normalize-positions@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/postcss-normalize-positions/-/postcss-normalize-positions-4.0.2.tgz#05f757f84f260437378368a91f8932d4b102917f" integrity sha512-Dlf3/9AxpxE+NF1fJxYDeggi5WwV35MXGFnnoccP/9qDtFrTArZ0D0R+iKcg5WsUd8nUYMIl8yXDCtcrT8JrdA== dependencies: cssnano-util-get-arguments "^4.0.0" has "^1.0.0" postcss "^7.0.0" postcss-value-parser "^3.0.0" postcss-normalize-repeat-style@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-4.0.2.tgz#c4ebbc289f3991a028d44751cbdd11918b17910c" integrity sha512-qvigdYYMpSuoFs3Is/f5nHdRLJN/ITA7huIoCyqqENJe9PvPmLhNLMu7QTjPdtnVf6OcYYO5SHonx4+fbJE1+Q== dependencies: cssnano-util-get-arguments "^4.0.0" cssnano-util-get-match "^4.0.0" postcss "^7.0.0" postcss-value-parser "^3.0.0" postcss-normalize-string@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/postcss-normalize-string/-/postcss-normalize-string-4.0.2.tgz#cd44c40ab07a0c7a36dc5e99aace1eca4ec2690c" integrity sha512-RrERod97Dnwqq49WNz8qo66ps0swYZDSb6rM57kN2J+aoyEAJfZ6bMx0sx/F9TIEX0xthPGCmeyiam/jXif0eA== dependencies: has "^1.0.0" postcss "^7.0.0" postcss-value-parser "^3.0.0" postcss-normalize-timing-functions@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-4.0.2.tgz#8e009ca2a3949cdaf8ad23e6b6ab99cb5e7d28d9" integrity sha512-acwJY95edP762e++00Ehq9L4sZCEcOPyaHwoaFOhIwWCDfik6YvqsYNxckee65JHLKzuNSSmAdxwD2Cud1Z54A== dependencies: cssnano-util-get-match "^4.0.0" postcss "^7.0.0" postcss-value-parser "^3.0.0" postcss-normalize-unicode@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/postcss-normalize-unicode/-/postcss-normalize-unicode-4.0.1.tgz#841bd48fdcf3019ad4baa7493a3d363b52ae1cfb" integrity sha512-od18Uq2wCYn+vZ/qCOeutvHjB5jm57ToxRaMeNuf0nWVHaP9Hua56QyMF6fs/4FSUnVIw0CBPsU0K4LnBPwYwg== dependencies: browserslist "^4.0.0" postcss "^7.0.0" postcss-value-parser "^3.0.0" postcss-normalize-url@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/postcss-normalize-url/-/postcss-normalize-url-4.0.1.tgz#10e437f86bc7c7e58f7b9652ed878daaa95faae1" integrity sha512-p5oVaF4+IHwu7VpMan/SSpmpYxcJMtkGppYf0VbdH5B6hN8YNmVyJLuY9FmLQTzY3fag5ESUUHDqM+heid0UVA== dependencies: is-absolute-url "^2.0.0" normalize-url "^3.0.0" postcss "^7.0.0" postcss-value-parser "^3.0.0" postcss-normalize-whitespace@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/postcss-normalize-whitespace/-/postcss-normalize-whitespace-4.0.2.tgz#bf1d4070fe4fcea87d1348e825d8cc0c5faa7d82" integrity sha512-tO8QIgrsI3p95r8fyqKV+ufKlSHh9hMJqACqbv2XknufqEDhDvbguXGBBqxw9nsQoXWf0qOqppziKJKHMD4GtA== dependencies: postcss "^7.0.0" postcss-value-parser "^3.0.0" postcss-ordered-values@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/postcss-ordered-values/-/postcss-ordered-values-4.1.2.tgz#0cf75c820ec7d5c4d280189559e0b571ebac0eee" integrity sha512-2fCObh5UanxvSxeXrtLtlwVThBvHn6MQcu4ksNT2tsaV2Fg76R2CV98W7wNSlX+5/pFwEyaDwKLLoEV7uRybAw== dependencies: cssnano-util-get-arguments "^4.0.0" postcss "^7.0.0" postcss-value-parser "^3.0.0" postcss-reduce-initial@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/postcss-reduce-initial/-/postcss-reduce-initial-4.0.3.tgz#7fd42ebea5e9c814609639e2c2e84ae270ba48df" integrity sha512-gKWmR5aUulSjbzOfD9AlJiHCGH6AEVLaM0AV+aSioxUDd16qXP1PCh8d1/BGVvpdWn8k/HiK7n6TjeoXN1F7DA== dependencies: browserslist "^4.0.0" caniuse-api "^3.0.0" has "^1.0.0" postcss "^7.0.0" postcss-reduce-transforms@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/postcss-reduce-transforms/-/postcss-reduce-transforms-4.0.2.tgz#17efa405eacc6e07be3414a5ca2d1074681d4e29" integrity sha512-EEVig1Q2QJ4ELpJXMZR8Vt5DQx8/mo+dGWSR7vWXqcob2gQLyQGsionYcGKATXvQzMPn6DSN1vTN7yFximdIAg== dependencies: cssnano-util-get-match "^4.0.0" has "^1.0.0" postcss "^7.0.0" postcss-value-parser "^3.0.0" postcss-selector-parser@^3.0.0: version "3.1.2" resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz#b310f5c4c0fdaf76f94902bbaa30db6aa84f5270" integrity sha512-h7fJ/5uWuRVyOtkO45pnt1Ih40CEleeyCHzipqAZO2e5H20g25Y48uYnFUiShvY4rZWNJ/Bib/KVPmanaCtOhA== dependencies: dot-prop "^5.2.0" indexes-of "^1.0.1" uniq "^1.0.1" postcss-selector-parser@^6.0.0, postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4: version "6.0.6" resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.6.tgz#2c5bba8174ac2f6981ab631a42ab0ee54af332ea" integrity sha512-9LXrvaaX3+mcv5xkg5kFwqSzSH1JIObIx51PrndZwlmznwXRfxMddDvo9gve3gVR8ZTKgoFDdWkbRFmEhT4PMg== dependencies: cssesc "^3.0.0" util-deprecate "^1.0.2" postcss-svgo@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-4.0.3.tgz#343a2cdbac9505d416243d496f724f38894c941e" integrity sha512-NoRbrcMWTtUghzuKSoIm6XV+sJdvZ7GZSc3wdBN0W19FTtp2ko8NqLsgoh/m9CzNhU3KLPvQmjIwtaNFkaFTvw== dependencies: postcss "^7.0.0" postcss-value-parser "^3.0.0" svgo "^1.0.0" postcss-unique-selectors@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/postcss-unique-selectors/-/postcss-unique-selectors-4.0.1.tgz#9446911f3289bfd64c6d680f073c03b1f9ee4bac" integrity sha512-+JanVaryLo9QwZjKrmJgkI4Fn8SBgRO6WXQBJi7KiAVPlmxikB5Jzc4EvXMT2H0/m0RjrVVm9rGNhZddm/8Spg== dependencies: alphanum-sort "^1.0.0" postcss "^7.0.0" uniqs "^2.0.0" postcss-value-parser@^3.0.0: version "3.3.1" resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz#9ff822547e2893213cf1c30efa51ac5fd1ba8281" integrity sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ== postcss-value-parser@^4.0.2, postcss-value-parser@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz#443f6a20ced6481a2bda4fa8532a6e55d789a2cb" integrity sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ== postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.27, postcss@^7.0.32, postcss@^7.0.5, postcss@^7.0.6: version "7.0.36" resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.36.tgz#056f8cffa939662a8f5905950c07d5285644dfcb" integrity sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw== dependencies: chalk "^2.4.2" source-map "^0.6.1" supports-color "^6.1.0" prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== prelude-ls@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= prepend-http@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897" integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc= prettier-bytes@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/prettier-bytes/-/prettier-bytes-1.0.4.tgz#994b02aa46f699c50b6257b5faaa7fe2557e62d6" integrity sha1-mUsCqkb2mcULYle1+qp/4lV+YtY= prettier-linter-helpers@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b" integrity sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w== dependencies: fast-diff "^1.1.2" prettier@~2.1.1: version "2.1.2" resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.1.2.tgz#3050700dae2e4c8b67c4c3f666cdb8af405e1ce5" integrity sha512-16c7K+x4qVlJg9rEbXl7HEGmQyZlG4R9AgP+oHKRMsMsuk8s+ATStlf1NpDqyBI1HpVyfjLOeMhH2LvuNvV5Vg== pretty-bytes@^5.1.0: version "5.6.0" resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb" integrity sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg== pretty-ms@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/pretty-ms/-/pretty-ms-7.0.1.tgz#7d903eaab281f7d8e03c66f867e239dc32fb73e8" integrity sha512-973driJZvxiGOQ5ONsFhOF/DtzPMOMtgC11kCpUrPGMTgqp2q/1gwzCquocrN33is0VZ5GFHXZYMM9l6h67v2Q== dependencies: parse-ms "^2.1.0" process-nextick-args@~2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== process@^0.11.10: version "0.11.10" resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI= progress@^2.0.0, progress@^2.0.1, progress@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== promise-deferred@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/promise-deferred/-/promise-deferred-2.0.3.tgz#b99c9588820798501862a593d49cece51d06fd7f" integrity sha512-n10XaoznCzLfyPFOlEE8iurezHpxrYzyjgq/1eW9Wk1gJwur/N7BdBmjJYJpqMeMcXK4wEbzo2EvZQcqjYcKUQ== dependencies: promise "^7.3.1" promise-fs@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/promise-fs/-/promise-fs-2.1.1.tgz#0b725a592c165ff16157d1f13640ba390637e557" integrity sha512-43p7e4QzAQ3w6eyN0+gbBL7jXiZFWLWYITg9wIObqkBySu/a5K1EDcQ/S6UyB/bmiZWDA4NjTbcopKLTaKcGSw== dependencies: "@octetstream/promisify" "2.0.2" promise-inflight@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" integrity sha1-mEcocL8igTL8vdhoEputEsPAKeM= promise-queue@^2.2.5: version "2.2.5" resolved "https://registry.yarnpkg.com/promise-queue/-/promise-queue-2.2.5.tgz#2f6f5f7c0f6d08109e967659c79b88a9ed5e93b4" integrity sha1-L29ffA9tCBCelnZZx5uIqe1ek7Q= promise-retry@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/promise-retry/-/promise-retry-2.0.1.tgz#ff747a13620ab57ba688f5fc67855410c370da22" integrity sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g== dependencies: err-code "^2.0.2" retry "^0.12.0" promise.series@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/promise.series/-/promise.series-0.2.0.tgz#2cc7ebe959fc3a6619c04ab4dbdc9e452d864bbd" integrity sha1-LMfr6Vn8OmYZwEq029yeRS2GS70= "promise@>=3.2 <8", promise@^7.3.1: version "7.3.1" resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf" integrity sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg== dependencies: asap "~2.0.3" promiseback@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/promiseback/-/promiseback-2.0.3.tgz#bd468d86930e8cd44bfc3292de9a6fbafb6378e6" integrity sha512-VZXdCwS0ppVNTIRfNsCvVwJAaP2b+pxQF7lM8DMWfmpNWyTxB6O5YNbzs+8z0ki/KIBHKHk308NTIl4kJUem3w== dependencies: is-callable "^1.1.5" promise-deferred "^2.0.3" promzard@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/promzard/-/promzard-0.3.0.tgz#26a5d6ee8c7dee4cb12208305acfb93ba382a9ee" integrity sha1-JqXW7ox97kyxIggwWs+5O6OCqe4= dependencies: read "1" proto-list@~1.2.1: version "1.2.4" resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849" integrity sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk= protocols@^1.1.0, protocols@^1.4.0: version "1.4.8" resolved "https://registry.yarnpkg.com/protocols/-/protocols-1.4.8.tgz#48eea2d8f58d9644a4a32caae5d5db290a075ce8" integrity sha512-IgjKyaUSjsROSO8/D49Ab7hP8mJgTYcqApOqdPhLoPxAplXmkp+zRvsrSQjFn5by0rhm4VH0GAUELIPpx7B1yg== proxy-addr@~2.0.5: version "2.0.7" resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== dependencies: forwarded "0.2.0" ipaddr.js "1.9.1" proxy-from-env@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== prr@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" integrity sha1-0/wRS6BplaRexok/SEzrHXj19HY= pseudomap@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM= psl@^1.1.24, psl@^1.1.28: version "1.8.0" resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ== public-encrypt@^4.0.0: version "4.0.3" resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.3.tgz#4fcc9d77a07e48ba7527e7cbe0de33d0701331e0" integrity sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q== dependencies: bn.js "^4.1.0" browserify-rsa "^4.0.0" create-hash "^1.1.0" parse-asn1 "^5.0.0" randombytes "^2.0.1" safe-buffer "^5.1.2" pump@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/pump/-/pump-2.0.1.tgz#12399add6e4cf7526d973cbc8b5ce2e2908b3909" integrity sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA== dependencies: end-of-stream "^1.1.0" once "^1.3.1" pump@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== dependencies: end-of-stream "^1.1.0" once "^1.3.1" pumpify@^1.3.3: version "1.5.1" resolved "https://registry.yarnpkg.com/pumpify/-/pumpify-1.5.1.tgz#36513be246ab27570b1a374a5ce278bfd74370ce" integrity sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ== dependencies: duplexify "^3.6.0" inherits "^2.0.3" pump "^2.0.0" punycode@1.3.2: version "1.3.2" resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" integrity sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0= punycode@^1.2.4, punycode@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= punycode@^2.1.0, punycode@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== pupa@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/pupa/-/pupa-2.1.1.tgz#f5e8fd4afc2c5d97828faa523549ed8744a20d62" integrity sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A== dependencies: escape-goat "^2.0.0" puppeteer@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/puppeteer/-/puppeteer-2.1.1.tgz#ccde47c2a688f131883b50f2d697bd25189da27e" integrity sha512-LWzaDVQkk1EPiuYeTOj+CZRIjda4k2s5w4MK4xoH2+kgWV/SDlkYHmxatDdtYrciHUKSXTsGgPgPP8ILVdBsxg== dependencies: "@types/mime-types" "^2.1.0" debug "^4.1.0" extract-zip "^1.6.6" https-proxy-agent "^4.0.0" mime "^2.0.3" mime-types "^2.1.25" progress "^2.0.1" proxy-from-env "^1.0.0" rimraf "^2.6.1" ws "^6.1.0" q@^1.1.2, q@^1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" integrity sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc= qjobs@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/qjobs/-/qjobs-1.2.0.tgz#c45e9c61800bd087ef88d7e256423bdd49e5d071" integrity sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg== qs@6.7.0: version "6.7.0" resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ== qs@^6.9.4: version "6.10.1" resolved "https://registry.yarnpkg.com/qs/-/qs-6.10.1.tgz#4931482fa8d647a5aab799c5271d2133b981fb6a" integrity sha512-M528Hph6wsSVOBiYUnGf+K/7w0hNshs/duGsNXPUCLH5XAqjEtiPGwNONLV0tBH8NoGb0mvD5JubnUTrujKDTg== dependencies: side-channel "^1.0.4" qs@~6.5.2: version "6.5.2" resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== query-string@^6.13.8: version "6.14.1" resolved "https://registry.yarnpkg.com/query-string/-/query-string-6.14.1.tgz#7ac2dca46da7f309449ba0f86b1fd28255b0c86a" integrity sha512-XDxAeVmpfu1/6IjyT/gXHOl+S0vQ9owggJ30hhWKdHAsNPOcasn5o9BW0eejZqL2e4vMjhAxoW3jVHcD6mbcYw== dependencies: decode-uri-component "^0.2.0" filter-obj "^1.1.0" split-on-first "^1.0.0" strict-uri-encode "^2.0.0" querystring-es3@^0.2.0: version "0.2.1" resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73" integrity sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM= querystring@0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA= queue-microtask@^1.2.2: version "1.2.3" resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== queue@^6.0.1: version "6.0.2" resolved "https://registry.yarnpkg.com/queue/-/queue-6.0.2.tgz#b91525283e2315c7553d2efa18d83e76432fed65" integrity sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA== dependencies: inherits "~2.0.3" quick-format-unescaped@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/quick-format-unescaped/-/quick-format-unescaped-4.0.3.tgz#6d6b66b8207aa2b35eef12be1421bb24c428f652" integrity sha512-MaL/oqh02mhEo5m5J2rwsVL23Iw2PEaGVHgT2vFt8AAsr0lfvQA5dpXo9TPu0rz7tSBdUPgkbam0j/fj5ZM8yg== quick-lru@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-4.0.1.tgz#5b8878f113a58217848c6482026c73e1ba57727f" integrity sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g== quick-lru@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932" integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA== randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5, randombytes@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== dependencies: safe-buffer "^5.1.0" randomfill@^1.0.3: version "1.0.4" resolved "https://registry.yarnpkg.com/randomfill/-/randomfill-1.0.4.tgz#c92196fc86ab42be983f1bf31778224931d61458" integrity sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw== dependencies: randombytes "^2.0.5" safe-buffer "^5.1.0" range-parser@^1.2.1, range-parser@~1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== raw-body@2.4.0: version "2.4.0" resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.0.tgz#a1ce6fb9c9bc356ca52e89256ab59059e13d0332" integrity sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q== dependencies: bytes "3.1.0" http-errors "1.7.2" iconv-lite "0.4.24" unpipe "1.0.0" rc@^1.2.8: version "1.2.8" resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== dependencies: deep-extend "^0.6.0" ini "~1.3.0" minimist "^1.2.0" strip-json-comments "~2.0.1" read-cmd-shim@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/read-cmd-shim/-/read-cmd-shim-2.0.0.tgz#4a50a71d6f0965364938e9038476f7eede3928d9" integrity sha512-HJpV9bQpkl6KwjxlJcBoqu9Ba0PQg8TqSNIOrulGt54a0uup0HtevreFHzYzkm0lpnleRdNBzXznKrgxglEHQw== read-package-json-fast@^2.0.1: version "2.0.3" resolved "https://registry.yarnpkg.com/read-package-json-fast/-/read-package-json-fast-2.0.3.tgz#323ca529630da82cb34b36cc0b996693c98c2b83" integrity sha512-W/BKtbL+dUjTuRL2vziuYhp76s5HZ9qQhd/dKfWIZveD0O40453QNyZhC0e63lqZrAQ4jiOapVoeJ7JrszenQQ== dependencies: json-parse-even-better-errors "^2.3.0" npm-normalize-package-bin "^1.0.1" read-package-json@^2.0.0: version "2.1.2" resolved "https://registry.yarnpkg.com/read-package-json/-/read-package-json-2.1.2.tgz#6992b2b66c7177259feb8eaac73c3acd28b9222a" integrity sha512-D1KmuLQr6ZSJS0tW8hf3WGpRlwszJOXZ3E8Yd/DNRaM5d+1wVRZdHlpGBLAuovjr28LbWvjpWkBHMxpRGGjzNA== dependencies: glob "^7.1.1" json-parse-even-better-errors "^2.3.0" normalize-package-data "^2.0.0" npm-normalize-package-bin "^1.0.0" read-package-json@^3.0.0, read-package-json@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/read-package-json/-/read-package-json-3.0.1.tgz#c7108f0b9390257b08c21e3004d2404c806744b9" integrity sha512-aLcPqxovhJTVJcsnROuuzQvv6oziQx4zd3JvG0vGCL5MjTONUc4uJ90zCBC6R7W7oUKBNoR/F8pkyfVwlbxqng== dependencies: glob "^7.1.1" json-parse-even-better-errors "^2.3.0" normalize-package-data "^3.0.0" npm-normalize-package-bin "^1.0.0" read-package-tree@^5.3.1: version "5.3.1" resolved "https://registry.yarnpkg.com/read-package-tree/-/read-package-tree-5.3.1.tgz#a32cb64c7f31eb8a6f31ef06f9cedf74068fe636" integrity sha512-mLUDsD5JVtlZxjSlPPx1RETkNjjvQYuweKwNVt1Sn8kP5Jh44pvYuUHCp6xSVDZWbNxVxG5lyZJ921aJH61sTw== dependencies: read-package-json "^2.0.0" readdir-scoped-modules "^1.0.0" util-promisify "^2.1.0" read-pkg-up@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-3.0.0.tgz#3ed496685dba0f8fe118d0691dc51f4a1ff96f07" integrity sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc= dependencies: find-up "^2.0.0" read-pkg "^3.0.0" read-pkg-up@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-7.0.1.tgz#f3a6135758459733ae2b95638056e1854e7ef507" integrity sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg== dependencies: find-up "^4.1.0" read-pkg "^5.2.0" type-fest "^0.8.1" read-pkg@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-3.0.0.tgz#9cbc686978fee65d16c00e2b19c237fcf6e38389" integrity sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k= dependencies: load-json-file "^4.0.0" normalize-package-data "^2.3.2" path-type "^3.0.0" read-pkg@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-5.2.0.tgz#7bf295438ca5a33e56cd30e053b34ee7250c93cc" integrity sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg== dependencies: "@types/normalize-package-data" "^2.4.0" normalize-package-data "^2.5.0" parse-json "^5.0.0" type-fest "^0.6.0" read@1, read@~1.0.1: version "1.0.7" resolved "https://registry.yarnpkg.com/read/-/read-1.0.7.tgz#b3da19bd052431a97671d44a42634adf710b40c4" integrity sha1-s9oZvQUkMal2cdRKQmNK33ELQMQ= dependencies: mute-stream "~0.0.4" "readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.6, readable-stream@~2.3.6: version "2.3.7" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== dependencies: core-util-is "~1.0.0" inherits "~2.0.3" isarray "~1.0.0" process-nextick-args "~2.0.0" safe-buffer "~5.1.1" string_decoder "~1.1.1" util-deprecate "~1.0.1" readable-stream@3, readable-stream@^3.0.0, readable-stream@^3.0.2, readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.5.0, readable-stream@^3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== dependencies: inherits "^2.0.3" string_decoder "^1.1.1" util-deprecate "^1.0.1" readdir-scoped-modules@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/readdir-scoped-modules/-/readdir-scoped-modules-1.1.0.tgz#8d45407b4f870a0dcaebc0e28670d18e74514309" integrity sha512-asaikDeqAQg7JifRsZn1NJZXo9E+VwlyCfbkZhwyISinqk5zNS6266HS5kah6P0SaQKGF6SkNnZVHUzHFYxYDw== dependencies: debuglog "^1.0.1" dezalgo "^1.0.0" graceful-fs "^4.1.2" once "^1.3.0" readdirp@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.2.1.tgz#0e87622a3325aa33e892285caf8b4e846529a525" integrity sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ== dependencies: graceful-fs "^4.1.11" micromatch "^3.1.10" readable-stream "^2.0.2" readdirp@~3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== dependencies: picomatch "^2.2.1" rechoir@^0.6.2: version "0.6.2" resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384" integrity sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q= dependencies: resolve "^1.1.6" redent@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/redent/-/redent-3.0.0.tgz#e557b7998316bb53c9f1f56fa626352c6963059f" integrity sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg== dependencies: indent-string "^4.0.0" strip-indent "^3.0.0" regex-not@^1.0.0, regex-not@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c" integrity sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A== dependencies: extend-shallow "^3.0.2" safe-regex "^1.1.0" regexpp@^3.0.0, regexpp@^3.1.0: version "3.2.0" resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== registry-auth-token@^4.0.0: version "4.2.1" resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-4.2.1.tgz#6d7b4006441918972ccd5fedcd41dc322c79b250" integrity sha512-6gkSb4U6aWJB4SF2ZvLb76yCBjcvufXBqvvEx1HbmKPkutswjW1xNVRY0+daljIYRbogN7O0etYSlbiaEQyMyw== dependencies: rc "^1.2.8" registry-url@^5.0.0: version "5.1.0" resolved "https://registry.yarnpkg.com/registry-url/-/registry-url-5.1.0.tgz#e98334b50d5434b81136b44ec638d9c2009c5009" integrity sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw== dependencies: rc "^1.2.8" remove-trailing-separator@^1.0.1: version "1.1.0" resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" integrity sha1-wkvOKig62tW8P1jg1IJJuSN52O8= repeat-element@^1.1.2: version "1.1.4" resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.4.tgz#be681520847ab58c7568ac75fbfad28ed42d39e9" integrity sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ== repeat-string@^1.6.1: version "1.6.1" resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= request-promise-core@1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.4.tgz#3eedd4223208d419867b78ce815167d10593a22f" integrity sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw== dependencies: lodash "^4.17.19" request-promise-native@^1.0.7: version "1.0.9" resolved "https://registry.yarnpkg.com/request-promise-native/-/request-promise-native-1.0.9.tgz#e407120526a5efdc9a39b28a5679bf47b9d9dc28" integrity sha512-wcW+sIUiWnKgNY0dqCpOZkUbF/I+YPi+f09JZIDa39Ec+q82CpSYniDp+ISgTTbKmnpJWASeJBPZmoxH84wt3g== dependencies: request-promise-core "1.1.4" stealthy-require "^1.1.1" tough-cookie "^2.3.3" request@2.88.0: version "2.88.0" resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef" integrity sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg== dependencies: aws-sign2 "~0.7.0" aws4 "^1.8.0" caseless "~0.12.0" combined-stream "~1.0.6" extend "~3.0.2" forever-agent "~0.6.1" form-data "~2.3.2" har-validator "~5.1.0" http-signature "~1.2.0" is-typedarray "~1.0.0" isstream "~0.1.2" json-stringify-safe "~5.0.1" mime-types "~2.1.19" oauth-sign "~0.9.0" performance-now "^2.1.0" qs "~6.5.2" safe-buffer "^5.1.2" tough-cookie "~2.4.3" tunnel-agent "^0.6.0" uuid "^3.3.2" request@2.88.2, request@^2.74.0, request@^2.88.0, request@^2.88.2: version "2.88.2" resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== dependencies: aws-sign2 "~0.7.0" aws4 "^1.8.0" caseless "~0.12.0" combined-stream "~1.0.6" extend "~3.0.2" forever-agent "~0.6.1" form-data "~2.3.2" har-validator "~5.1.3" http-signature "~1.2.0" is-typedarray "~1.0.0" isstream "~0.1.2" json-stringify-safe "~5.0.1" mime-types "~2.1.19" oauth-sign "~0.9.0" performance-now "^2.1.0" qs "~6.5.2" safe-buffer "^5.1.2" tough-cookie "~2.5.0" tunnel-agent "^0.6.0" uuid "^3.3.2" require-directory@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= require-main-filename@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== requires-port@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8= resolve-alpn@^1.0.0: version "1.2.0" resolved "https://registry.yarnpkg.com/resolve-alpn/-/resolve-alpn-1.2.0.tgz#058bb0888d1cd4d12474e9a4b6eb17bdd5addc44" integrity sha512-e4FNQs+9cINYMO5NMFc6kOUCdohjqFPSgMuwuZAOUWqrfWsen+Yjy5qZFkV5K7VO7tFSLKcUL97olkED7sCBHA== resolve-cwd@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a" integrity sha1-AKn3OHVW4nA46uIyyqNypqWbZlo= dependencies: resolve-from "^3.0.0" resolve-cwd@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== dependencies: resolve-from "^5.0.0" resolve-dir@^1.0.0, resolve-dir@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/resolve-dir/-/resolve-dir-1.0.1.tgz#79a40644c362be82f26effe739c9bb5382046f43" integrity sha1-eaQGRMNivoLybv/nOcm7U4IEb0M= dependencies: expand-tilde "^2.0.0" global-modules "^1.0.0" resolve-from@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748" integrity sha1-six699nWiBvItuZTM17rywoYh0g= resolve-from@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== resolve-from@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== resolve-url@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= resolve@^1.1.6, resolve@^1.10.0, resolve@^1.11.1, resolve@^1.13.1, resolve@^1.19.0, resolve@^1.20.0: version "1.20.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975" integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A== dependencies: is-core-module "^2.2.0" path-parse "^1.0.6" resolve@~1.17.0: version "1.17.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.17.0.tgz#b25941b54968231cc2d1bb76a79cb7f2c0bf8444" integrity sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w== dependencies: path-parse "^1.0.6" resolve@~1.19.0: version "1.19.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.19.0.tgz#1af5bf630409734a067cae29318aac7fa29a267c" integrity sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg== dependencies: is-core-module "^2.1.0" path-parse "^1.0.6" responselike@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7" integrity sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec= dependencies: lowercase-keys "^1.0.0" responselike@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/responselike/-/responselike-2.0.0.tgz#26391bcc3174f750f9a79eacc40a12a5c42d7723" integrity sha512-xH48u3FTB9VsZw7R+vvgaKeLKzT6jOogbQhEe/jewwnZgzPcnyWui2Av6JpoYZF/91uueC+lqhWqeURw5/qhCw== dependencies: lowercase-keys "^2.0.0" restore-cursor@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e" integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA== dependencies: onetime "^5.1.0" signal-exit "^3.0.2" ret@~0.1.10: version "0.1.15" resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== retry@^0.10.0: version "0.10.1" resolved "https://registry.yarnpkg.com/retry/-/retry-0.10.1.tgz#e76388d217992c252750241d3d3956fed98d8ff4" integrity sha1-52OI0heZLCUnUCQdPTlW/tmNj/Q= retry@^0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" integrity sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs= reusify@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== rfdc@^1.1.4: version "1.3.0" resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.3.0.tgz#d0b7c441ab2720d05dc4cf26e01c89631d9da08b" integrity sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA== rgb-regex@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/rgb-regex/-/rgb-regex-1.0.1.tgz#c0e0d6882df0e23be254a475e8edd41915feaeb1" integrity sha1-wODWiC3w4jviVKR16O3UGRX+rrE= rgba-regex@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/rgba-regex/-/rgba-regex-1.0.0.tgz#43374e2e2ca0968b0ef1523460b7d730ff22eeb3" integrity sha1-QzdOLiyglosO8VI0YLfXMP8i7rM= rimraf@2.6.3: version "2.6.3" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA== dependencies: glob "^7.1.3" rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.3: version "2.7.1" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== dependencies: glob "^7.1.3" rimraf@^3.0.0, rimraf@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== dependencies: glob "^7.1.3" rimraf@~2.4.0: version "2.4.5" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.4.5.tgz#ee710ce5d93a8fdb856fb5ea8ff0e2d75934b2da" integrity sha1-7nEM5dk6j9uFb7Xqj/Di11k0sto= dependencies: glob "^6.0.1" ripemd160@^2.0.0, ripemd160@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c" integrity sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA== dependencies: hash-base "^3.0.0" inherits "^2.0.1" roarr@^2.15.3: version "2.15.4" resolved "https://registry.yarnpkg.com/roarr/-/roarr-2.15.4.tgz#f5fe795b7b838ccfe35dc608e0282b9eba2e7afd" integrity sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A== dependencies: boolean "^3.0.1" detect-node "^2.0.4" globalthis "^1.0.1" json-stringify-safe "^5.0.1" semver-compare "^1.0.0" sprintf-js "^1.1.2" rollup-plugin-node-resolve@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/rollup-plugin-node-resolve/-/rollup-plugin-node-resolve-5.2.0.tgz#730f93d10ed202473b1fb54a5997a7db8c6d8523" integrity sha512-jUlyaDXts7TW2CqQ4GaO5VJ4PwwaV8VUGA7+km3n6k6xtOEacf61u0VXwN80phY/evMcaS+9eIeJ9MOyDxt5Zw== dependencies: "@types/resolve" "0.0.8" builtin-modules "^3.1.0" is-module "^1.0.0" resolve "^1.11.1" rollup-pluginutils "^2.8.1" rollup-plugin-postcss@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/rollup-plugin-postcss/-/rollup-plugin-postcss-4.0.0.tgz#2131fb6db0d5dce01a37235e4f6ad4523c681cea" integrity sha512-OQzT+YspV01/6dxfyEw8lBO2px3hyL8Xn+k2QGctL7V/Yx2Z1QaMKdYVslP1mqv7RsKt6DROIlnbpmgJ3yxf6g== dependencies: chalk "^4.1.0" concat-with-sourcemaps "^1.1.0" cssnano "^4.1.10" import-cwd "^3.0.0" p-queue "^6.6.2" pify "^5.0.0" postcss-load-config "^3.0.0" postcss-modules "^4.0.0" promise.series "^0.2.0" resolve "^1.19.0" rollup-pluginutils "^2.8.2" safe-identifier "^0.4.2" style-inject "^0.3.0" rollup-plugin-sourcemaps@^0.6.3: version "0.6.3" resolved "https://registry.yarnpkg.com/rollup-plugin-sourcemaps/-/rollup-plugin-sourcemaps-0.6.3.tgz#bf93913ffe056e414419607f1d02780d7ece84ed" integrity sha512-paFu+nT1xvuO1tPFYXGe+XnQvg4Hjqv/eIhG8i5EspfYYPBKL57X7iVbfv55aNVASg3dzWvES9dmWsL2KhfByw== dependencies: "@rollup/pluginutils" "^3.0.9" source-map-resolve "^0.6.0" rollup-pluginutils@^2.8.1, rollup-pluginutils@^2.8.2: version "2.8.2" resolved "https://registry.yarnpkg.com/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz#72f2af0748b592364dbd3389e600e5a9444a351e" integrity sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ== dependencies: estree-walker "^0.6.1" rollup@^2.56.0: version "2.56.0" resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.56.0.tgz#daa832955d2b58f1ed52a3c4c85b7d1adaf076d0" integrity sha512-weEafgbjbHCnrtJPNyCrhYnjP62AkF04P0BcV/1mofy1+gytWln4VVB1OK462cq2EAyWzRDpTMheSP/o+quoiA== optionalDependencies: fsevents "~2.3.2" run-async@^2.4.0: version "2.4.1" resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455" integrity sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ== run-parallel@^1.1.9: version "1.2.0" resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== dependencies: queue-microtask "^1.2.2" run-queue@^1.0.0, run-queue@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/run-queue/-/run-queue-1.0.3.tgz#e848396f057d223f24386924618e25694161ec47" integrity sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec= dependencies: aproba "^1.1.1" rxjs@^6.6.0, rxjs@^6.6.7: version "6.6.7" resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9" integrity sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ== dependencies: tslib "^1.9.0" safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.2" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@^5.2.1, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== safe-identifier@^0.4.2: version "0.4.2" resolved "https://registry.yarnpkg.com/safe-identifier/-/safe-identifier-0.4.2.tgz#cf6bfca31c2897c588092d1750d30ef501d59fcb" integrity sha512-6pNbSMW6OhAi9j+N8V+U715yBQsaWJ7eyEUaOrawX+isg5ZxhUlV1NipNtgaKHmFGiABwt+ZF04Ii+3Xjkg+8w== safe-regex@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" integrity sha1-QKNmnzsHfR6UPURinhV91IAjvy4= dependencies: ret "~0.1.10" "safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== sax@>=0.6.0, sax@^1.2.4, sax@~1.2.4: version "1.2.4" resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== saxes@^3.1.9: version "3.1.11" resolved "https://registry.yarnpkg.com/saxes/-/saxes-3.1.11.tgz#d59d1fd332ec92ad98a2e0b2ee644702384b1c5b" integrity sha512-Ydydq3zC+WYDJK1+gRxRapLIED9PWeSuuS41wqyoRmzvhhh9nc+QQrVMKJYzJFULazeGhzSV0QleN2wD3boh2g== dependencies: xmlchars "^2.1.1" schema-utils@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-1.0.0.tgz#0b79a93204d7b600d4b2850d1f66c2a34951c770" integrity sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g== dependencies: ajv "^6.1.0" ajv-errors "^1.0.0" ajv-keywords "^3.1.0" schema-utils@^2.5.0, schema-utils@^2.7.0: version "2.7.1" resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.7.1.tgz#1ca4f32d1b24c590c203b8e7a50bf0ea4cd394d7" integrity sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg== dependencies: "@types/json-schema" "^7.0.5" ajv "^6.12.4" ajv-keywords "^3.5.2" semver-compare@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc" integrity sha1-De4hahyUGrN+nvsXiPavxf9VN/w= semver-diff@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-3.1.1.tgz#05f77ce59f325e00e2706afd67bb506ddb1ca32b" integrity sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg== dependencies: semver "^6.3.0" semver-regex@^3.1.2: version "3.1.3" resolved "https://registry.yarnpkg.com/semver-regex/-/semver-regex-3.1.3.tgz#b2bcc6f97f63269f286994e297e229b6245d0dc3" integrity sha512-Aqi54Mk9uYTjVexLnR67rTyBusmwd04cLkHy9hNvk3+G3nT2Oyg7E0l4XVbOaNwIvQ3hHeYxGcyEy+mKreyBFQ== "semver@2 >=2.2.1 || 3.x || 4 || 5", "semver@2 || 3 || 4 || 5", semver@^5.5.0, semver@^5.5.1, semver@^5.6.0, semver@^5.7.1: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== semver@7.3.5, semver@^7.0.0, semver@^7.1.1, semver@^7.1.2, semver@^7.1.3, semver@^7.2.1, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@~7.3.0: version "7.3.5" resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== dependencies: lru-cache "^6.0.0" semver@^6.0.0, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0: version "6.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== send@0.17.1: version "0.17.1" resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8" integrity sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg== dependencies: debug "2.6.9" depd "~1.1.2" destroy "~1.0.4" encodeurl "~1.0.2" escape-html "~1.0.3" etag "~1.8.1" fresh "0.5.2" http-errors "~1.7.2" mime "1.6.0" ms "2.1.1" on-finished "~2.3.0" range-parser "~1.2.1" statuses "~1.5.0" serialize-error@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/serialize-error/-/serialize-error-7.0.1.tgz#f1360b0447f61ffb483ec4157c737fab7d778e18" integrity sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw== dependencies: type-fest "^0.13.1" serialize-javascript@6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.0.tgz#efae5d88f45d7924141da8b5c3a7a7e663fefeb8" integrity sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag== dependencies: randombytes "^2.1.0" serialize-javascript@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-4.0.0.tgz#b525e1238489a5ecfc42afacc3fe99e666f4b1aa" integrity sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw== dependencies: randombytes "^2.1.0" serve-static@1.14.1: version "1.14.1" resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.1.tgz#666e636dc4f010f7ef29970a88a674320898b2f9" integrity sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg== dependencies: encodeurl "~1.0.2" escape-html "~1.0.3" parseurl "~1.3.3" send "0.17.1" set-blocking@^2.0.0, set-blocking@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= set-immediate-shim@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz#4b2b1b27eb808a9f8dcc481a58e5e56f599f3f61" integrity sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E= set-value@^2.0.0, set-value@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b" integrity sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw== dependencies: extend-shallow "^2.0.1" is-extendable "^0.1.1" is-plain-object "^2.0.3" split-string "^3.0.1" setimmediate@^1.0.4: version "1.0.5" resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU= setprototypeof@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683" integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw== setprototypeof@1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== sha.js@^2.4.0, sha.js@^2.4.8: version "2.4.11" resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7" integrity sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ== dependencies: inherits "^2.0.1" safe-buffer "^5.0.1" shallow-clone@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3" integrity sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA== dependencies: kind-of "^6.0.2" shebang-command@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" integrity sha1-RKrGW2lbAzmJaMOfNj/uXer98eo= dependencies: shebang-regex "^1.0.0" shebang-command@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== dependencies: shebang-regex "^3.0.0" shebang-regex@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= shebang-regex@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== shell-quote@^1.7.2: version "1.7.2" resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.7.2.tgz#67a7d02c76c9da24f99d20808fcaded0e0e04be2" integrity sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg== shelljs@^0.8.3: version "0.8.4" resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.4.tgz#de7684feeb767f8716b326078a8a00875890e3c2" integrity sha512-7gk3UZ9kOfPLIAbslLzyWeGiEqx9e3rxwZM0KE6EL8GlGwjym9Mrlx5/p33bWTu9YG6vcS4MBxYZDHYr5lr8BQ== dependencies: glob "^7.0.0" interpret "^1.0.0" rechoir "^0.6.2" side-channel@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== dependencies: call-bind "^1.0.0" get-intrinsic "^1.0.2" object-inspect "^1.9.0" signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA== simple-swizzle@^0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" integrity sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo= dependencies: is-arrayish "^0.3.1" simulate-event@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/simulate-event/-/simulate-event-1.4.0.tgz#7f8a404116280bcbfe26347ddbcbffe5bd2be00e" integrity sha1-f4pAQRYoC8v+JjR928v/5b0r4A4= dependencies: xtend "^4.0.1" slash@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== slice-ansi@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-2.1.0.tgz#cacd7693461a637a5788d92a7dd4fba068e81636" integrity sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ== dependencies: ansi-styles "^3.2.0" astral-regex "^1.0.0" is-fullwidth-code-point "^2.0.0" slice-ansi@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-3.0.0.tgz#31ddc10930a1b7e0b67b08c96c2f49b77a789787" integrity sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ== dependencies: ansi-styles "^4.0.0" astral-regex "^2.0.0" is-fullwidth-code-point "^3.0.0" slice-ansi@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-4.0.0.tgz#500e8dd0fd55b05815086255b3195adf2a45fe6b" integrity sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ== dependencies: ansi-styles "^4.0.0" astral-regex "^2.0.0" is-fullwidth-code-point "^3.0.0" slide@^1.1.3, slide@^1.1.6: version "1.1.6" resolved "https://registry.yarnpkg.com/slide/-/slide-1.1.6.tgz#56eb027d65b4d2dce6cb2e2d32c4d4afc9e1d707" integrity sha1-VusCfWW00tzmyy4tMsTUr8nh1wc= smart-buffer@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.1.0.tgz#91605c25d91652f4661ea69ccf45f1b331ca21ba" integrity sha512-iVICrxOzCynf/SNaBQCw34eM9jROU/s5rzIhpOvzhzuYHfJR/DhZfDkXiZSgKXfgv26HT3Yni3AV/DGw0cGnnw== snapdragon-node@^2.0.1: version "2.1.1" resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" integrity sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw== dependencies: define-property "^1.0.0" isobject "^3.0.0" snapdragon-util "^3.0.1" snapdragon-util@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/snapdragon-util/-/snapdragon-util-3.0.1.tgz#f956479486f2acd79700693f6f7b805e45ab56e2" integrity sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ== dependencies: kind-of "^3.2.0" snapdragon@^0.8.1: version "0.8.2" resolved "https://registry.yarnpkg.com/snapdragon/-/snapdragon-0.8.2.tgz#64922e7c565b0e14204ba1aa7d6964278d25182d" integrity sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg== dependencies: base "^0.11.1" debug "^2.2.0" define-property "^0.2.5" extend-shallow "^2.0.1" map-cache "^0.2.2" source-map "^0.5.6" source-map-resolve "^0.5.0" use "^3.1.0" snyk-config@4.0.0, snyk-config@^4.0.0-rc.2: version "4.0.0" resolved "https://registry.yarnpkg.com/snyk-config/-/snyk-config-4.0.0.tgz#21d459f19087991246cc07a7ffb4501dce6f4159" integrity sha512-E6jNe0oUjjzVASWBOAc/mA23DhbzABDF9MI6UZvl0gylh2NSXSXw2/LjlqMNOKL2c1qkbSkzLOdIX5XACoLCAQ== dependencies: async "^3.2.0" debug "^4.1.1" lodash.merge "^4.6.2" minimist "^1.2.5" snyk-cpp-plugin@2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/snyk-cpp-plugin/-/snyk-cpp-plugin-2.2.1.tgz#55891511a43a6448e5a7c836a94f66f70fa705eb" integrity sha512-NFwVLMCqKTocY66gcim0ukF6e31VRDJqDapg5sy3vCHqlD1OCNUXSK/aI4VQEEndDrsnFmQepsL5KpEU0dDRIQ== dependencies: "@snyk/dep-graph" "^1.19.3" chalk "^4.1.0" debug "^4.1.1" hosted-git-info "^3.0.7" tslib "^2.0.0" snyk-docker-plugin@4.22.1: version "4.22.1" resolved "https://registry.yarnpkg.com/snyk-docker-plugin/-/snyk-docker-plugin-4.22.1.tgz#09283bfb7beb98553abde0719e96bccfd5bac0d9" integrity sha512-fpXGkBu69Vb5meSrq0KjSKr0nlibA8z18fuH/O8HuDh1b5XyqKNz412njybpJtW07JPpA9rKX9gewRBZWch6fQ== dependencies: "@snyk/dep-graph" "^1.28.0" "@snyk/rpm-parser" "^2.0.0" "@snyk/snyk-docker-pull" "^3.6.3" chalk "^2.4.2" debug "^4.1.1" docker-modem "2.1.3" dockerfile-ast "0.2.1" elfy "^1.0.0" event-loop-spinner "^2.0.0" gunzip-maybe "^1.4.2" mkdirp "^1.0.4" semver "^7.3.4" snyk-nodejs-lockfile-parser "1.35.1" tar-stream "^2.1.0" tmp "^0.2.1" tslib "^1" uuid "^8.2.0" snyk-go-parser@1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/snyk-go-parser/-/snyk-go-parser-1.4.1.tgz#df16a5fbd7a517ee757268ef081abc33506c8857" integrity sha512-StU3uHB85VMEkcgXta63M0Fgd+9cs5sMCjQXTBoYTdE4dxarPn7U67yCuwkRRdZdny1ZXtzfY8LKns9i0+dy9w== dependencies: toml "^3.0.0" tslib "^1.10.0" snyk-go-plugin@1.17.0: version "1.17.0" resolved "https://registry.yarnpkg.com/snyk-go-plugin/-/snyk-go-plugin-1.17.0.tgz#56d0c92d7def29ba4c3c2030c5830093e3b0dd26" integrity sha512-1jAYPRgMapO2BYL+HWsUq5gsAiDGmI0Pn7omc0lk24tcUOMhUB+1hb0u9WBMNzHvXBjevBkjOctjpnt2hMKN6Q== dependencies: "@snyk/dep-graph" "^1.23.1" "@snyk/graphlib" "2.1.9-patch.3" debug "^4.1.1" snyk-go-parser "1.4.1" tmp "0.2.1" tslib "^1.10.0" snyk-gradle-plugin@3.16.1: version "3.16.1" resolved "https://registry.yarnpkg.com/snyk-gradle-plugin/-/snyk-gradle-plugin-3.16.1.tgz#e180720d521ae1a63efb4b7f35c084af47ac2cb8" integrity sha512-ii+W544+vCsRe+I4FdmhnYwGq5ZZYacEkUswJoUYmj1sIkkN1G0iUyas/r9mX+ERjQlvzyN4diptZe9OeaTaaA== dependencies: "@snyk/cli-interface" "2.11.0" "@snyk/dep-graph" "^1.28.0" "@snyk/java-call-graph-builder" "1.23.1" "@types/debug" "^4.1.4" chalk "^3.0.0" debug "^4.1.1" tmp "0.2.1" tslib "^2.0.0" snyk-module@3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/snyk-module/-/snyk-module-3.1.0.tgz#3e088ff473ddf0d4e253a46ea6749d76d8e6e7ba" integrity sha512-HHuOYEAACpUpkFgU8HT57mmxmonaJ4O3YADoSkVhnhkmJ+AowqZyJOau703dYHNrq2DvQ7qYw81H7yyxS1Nfjw== dependencies: debug "^4.1.1" hosted-git-info "^3.0.4" snyk-module@^3.0.0, snyk-module@^3.1.0: version "3.2.0" resolved "https://registry.yarnpkg.com/snyk-module/-/snyk-module-3.2.0.tgz#11876c46c79fb1bae71f56e16f2c53ce62dd0db6" integrity sha512-6MLJyi4OMOZtCWTzGgRMEEw9qQ1fAwKoj5XYXfKOjIsohi3ubKsVfvSoScj0IovtiKowm2iCZ+VIRPJab6nCxA== dependencies: debug "^4.1.1" hosted-git-info "^4.0.2" snyk-mvn-plugin@2.26.2: version "2.26.2" resolved "https://registry.yarnpkg.com/snyk-mvn-plugin/-/snyk-mvn-plugin-2.26.2.tgz#433aaab1cb5b1aeb5591ac67acb668632b148f08" integrity sha512-XS6I10OYMzUq60DUqf0Lf4m8uLXZTFH58O++n5W/X4MtSmYV4frrpgZOrrDfzxBM5S7SH9FlKDL3p+1m84yqzg== dependencies: "@snyk/cli-interface" "2.11.0" "@snyk/dep-graph" "^1.23.1" "@snyk/java-call-graph-builder" "1.23.1" debug "^4.1.1" glob "^7.1.6" needle "^2.5.0" tmp "^0.1.0" tslib "1.11.1" snyk-nodejs-lockfile-parser@1.35.0: version "1.35.0" resolved "https://registry.yarnpkg.com/snyk-nodejs-lockfile-parser/-/snyk-nodejs-lockfile-parser-1.35.0.tgz#1cdf13abd05dc5e560e513936cb766f6ed6abe97" integrity sha512-fSjer9Ic8cdA2HvInUmhwbAhoLFXIokAzGB1PeGKwr0zzyfo3dSX3ReTMEbkhrEg+h0eES13px/KiiJ0EKRKMg== dependencies: "@snyk/graphlib" "2.1.9-patch.3" "@yarnpkg/core" "^2.4.0" "@yarnpkg/lockfile" "^1.1.0" event-loop-spinner "^2.0.0" got "11.8.2" js-yaml "^4.1.0" lodash.clonedeep "^4.5.0" lodash.flatmap "^4.5.0" lodash.isempty "^4.4.0" lodash.set "^4.3.2" lodash.topairs "^4.3.0" p-map "2.1.0" snyk-config "^4.0.0-rc.2" tslib "^1.9.3" uuid "^8.3.0" snyk-nodejs-lockfile-parser@1.35.1: version "1.35.1" resolved "https://registry.yarnpkg.com/snyk-nodejs-lockfile-parser/-/snyk-nodejs-lockfile-parser-1.35.1.tgz#bd7da418637acadbbb011ee03e1db3c9ba998842" integrity sha512-NiXN+MdWaZxseXVDgCM4CZ5aBgI5LloUbwUP9c3oMZDih9Zj6Vf5edDcL8eM3BGl+a6LceJzB6w+xrIqKCXgQA== dependencies: "@snyk/graphlib" "2.1.9-patch.3" "@yarnpkg/core" "^2.4.0" "@yarnpkg/lockfile" "^1.1.0" event-loop-spinner "^2.0.0" js-yaml "^4.1.0" lodash.clonedeep "^4.5.0" lodash.flatmap "^4.5.0" lodash.isempty "^4.4.0" lodash.set "^4.3.2" lodash.topairs "^4.3.0" snyk-config "^4.0.0-rc.2" tslib "^1.9.3" uuid "^8.3.0" snyk-nuget-plugin@1.22.0: version "1.22.0" resolved "https://registry.yarnpkg.com/snyk-nuget-plugin/-/snyk-nuget-plugin-1.22.0.tgz#05c4aab0ebf1b74cea966503b8c272aee8502d9c" integrity sha512-R0pmcEYeoM3B6BUMUf30jPQgQo8ngHW0gAabyGMnBV3ZDvJ99TCa7McSIjI/3obdT1ERIKKF6bZxuzps4uzVOA== dependencies: debug "^4.1.1" dotnet-deps-parser "5.1.0" jszip "3.7.0" snyk-paket-parser "1.6.0" tslib "^1.11.2" xml2js "^0.4.17" snyk-paket-parser@1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/snyk-paket-parser/-/snyk-paket-parser-1.6.0.tgz#f70c423b33d31484c8c4cae74bb7f5deb9bbc382" integrity sha512-6htFynjBe/nakclEHUZ1A3j5Eu32/0pNve5Qm4MFn3YQmJgj7UcAO8hdyK3QfzEY29/kAv/rkJQg+SKshn+N9Q== dependencies: tslib "^1.9.3" snyk-php-plugin@1.9.2: version "1.9.2" resolved "https://registry.yarnpkg.com/snyk-php-plugin/-/snyk-php-plugin-1.9.2.tgz#282ef733060aab49da23e1fb2d2dd1af8f71f7cd" integrity sha512-IQcdsQBqqXVRY5DatlI7ASy4flbhtU2V7cr4P2rK9rkFnVHO6LHcitwKXVZa9ocdOmpZDzk7U6iwHJkVFcR6OA== dependencies: "@snyk/cli-interface" "^2.9.1" "@snyk/composer-lockfile-parser" "^1.4.1" tslib "1.11.1" snyk-poetry-lockfile-parser@^1.1.6: version "1.1.6" resolved "https://registry.yarnpkg.com/snyk-poetry-lockfile-parser/-/snyk-poetry-lockfile-parser-1.1.6.tgz#bab5a279c103cbcca8eb86ab87717b115592881e" integrity sha512-MoekbWOZPj9umfukjk2bd2o3eRj0OyO+58sxq9crMtHmTlze4h0/Uj4+fb0JFPBOtBO3c2zwbA+dvFQmpKoOTA== dependencies: "@snyk/cli-interface" "^2.9.2" "@snyk/dep-graph" "^1.23.0" debug "^4.2.0" toml "^3.0.0" tslib "^2.0.0" snyk-policy@1.19.0: version "1.19.0" resolved "https://registry.yarnpkg.com/snyk-policy/-/snyk-policy-1.19.0.tgz#0cbc442d9503970fb3afea938f57d57993a914ad" integrity sha512-XYjhOTRPFA7NfDUsH6uH1fbML2OgSFsqdUPbud7x01urNP9CHXgUgAD4NhKMi3dVQK+7IdYadWt0wrFWw4y+qg== dependencies: debug "^4.1.1" email-validator "^2.0.4" js-yaml "^3.13.1" lodash.clonedeep "^4.5.0" promise-fs "^2.1.1" semver "^6.0.0" snyk-module "^3.0.0" snyk-resolve "^1.1.0" snyk-try-require "^2.0.0" snyk-python-plugin@1.19.11: version "1.19.11" resolved "https://registry.yarnpkg.com/snyk-python-plugin/-/snyk-python-plugin-1.19.11.tgz#4ccb213f218606c386ef943113d0872e69c34f09" integrity sha512-zUKbSbw+wU1FCUDYt+IDjaES0pc1UKBECOqjHSJMxWm9VhstvPtI4KccetwOfne2oUcmaEJJvcEp4s9VTK04XQ== dependencies: "@snyk/cli-interface" "^2.0.3" snyk-poetry-lockfile-parser "^1.1.6" tmp "0.2.1" snyk-resolve-deps@4.7.2: version "4.7.2" resolved "https://registry.yarnpkg.com/snyk-resolve-deps/-/snyk-resolve-deps-4.7.2.tgz#11e7051110dadd8756819594bb30e6b88777a8b4" integrity sha512-Bmtr7QdRL2b3Js+mPDmvXbkprOpzO8aUFXqR0nJKAOlUVQqZ84yiuT0n/mssEiJJ0vP+k0kZvTeiTwgio4KZRg== dependencies: ansicolors "^0.3.2" debug "^4.1.1" lodash.assign "^4.2.0" lodash.assignin "^4.2.0" lodash.clone "^4.5.0" lodash.flatten "^4.4.0" lodash.get "^4.4.2" lodash.set "^4.3.2" lru-cache "^4.0.0" semver "^5.5.1" snyk-module "^3.1.0" snyk-resolve "^1.0.0" snyk-tree "^1.0.0" snyk-try-require "^1.1.1" then-fs "^2.0.0" snyk-resolve@1.1.0, snyk-resolve@^1.0.0, snyk-resolve@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/snyk-resolve/-/snyk-resolve-1.1.0.tgz#52740cb01ba477851086855f9857b3a44296ee0e" integrity sha512-OZMF8I8TOu0S58Z/OS9mr8jkEzGAPByCsAkrWlcmZgPaE0RsxVKVIFPhbMNy/JlYswgGDYYIEsNw+e0j1FnTrw== dependencies: debug "^4.1.1" promise-fs "^2.1.1" snyk-sbt-plugin@2.11.3: version "2.11.3" resolved "https://registry.yarnpkg.com/snyk-sbt-plugin/-/snyk-sbt-plugin-2.11.3.tgz#5740a5d9edfea79b380040ad163683d45658f748" integrity sha512-xcZAYENuEx+SG51AuLLL59jpN/qerJdSdznTANoyNM7bJjVhTvLTjEfoOxbeogZwKmFDKKUfc6Vw+EdEy8VZug== dependencies: debug "^4.1.1" semver "^6.1.2" tmp "^0.1.0" tree-kill "^1.2.2" tslib "^1.10.0" snyk-tree@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/snyk-tree/-/snyk-tree-1.0.0.tgz#0fb73176dbf32e782f19100294160448f9111cc8" integrity sha1-D7cxdtvzLngvGRAClBYESPkRHMg= dependencies: archy "^1.0.0" snyk-try-require@1.3.1, snyk-try-require@^1.1.1: version "1.3.1" resolved "https://registry.yarnpkg.com/snyk-try-require/-/snyk-try-require-1.3.1.tgz#6e026f92e64af7fcccea1ee53d524841e418a212" integrity sha1-bgJvkuZK9/zM6h7lPVJIQeQYohI= dependencies: debug "^3.1.0" lodash.clonedeep "^4.3.0" lru-cache "^4.0.0" then-fs "^2.0.0" snyk-try-require@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/snyk-try-require/-/snyk-try-require-2.0.1.tgz#076ae9bc505d64d28389452ce19fcac28f26655a" integrity sha512-VCOfFIvqLMXgCXEdooQgu3A40XYIFBnj0X8Y01RJ5iAbu08b4WKGN/uAKaRVF30dABS4EcjsalmCO+YlKUPEIA== dependencies: debug "^4.1.1" lodash.clonedeep "^4.3.0" lru-cache "^5.1.1" snyk@^1.91.0: version "1.675.0" resolved "https://registry.yarnpkg.com/snyk/-/snyk-1.675.0.tgz#fb89421fbd5d3d25f813589ad4bd4396a2a04484" integrity sha512-J5ZvEiaAIRdyFrIjS1OnfQs5vKLG0SNPJmeg/GuTA9z8L/gqtTXUsnCrnmpQ8Y2y7L3LRyjh9VXx7G0QC9rlWA== dependencies: "@open-policy-agent/opa-wasm" "^1.2.0" "@snyk/cli-interface" "2.11.0" "@snyk/cloud-config-parser" "^1.9.2" "@snyk/code-client" "4.0.0" "@snyk/dep-graph" "^1.27.1" "@snyk/fix" "1.650.0" "@snyk/gemfile" "1.2.0" "@snyk/graphlib" "^2.1.9-patch.3" "@snyk/inquirer" "^7.3.3-patch" "@snyk/snyk-cocoapods-plugin" "2.5.2" "@snyk/snyk-hex-plugin" "1.1.4" abbrev "^1.1.1" ansi-escapes "3.2.0" chalk "^2.4.2" cli-spinner "0.2.10" configstore "^5.0.1" debug "^4.1.1" diff "^4.0.1" glob "^7.1.7" global-agent "^2.1.12" lodash.assign "^4.2.0" lodash.camelcase "^4.3.0" lodash.clonedeep "^4.5.0" lodash.flatten "^4.4.0" lodash.flattendeep "^4.4.0" lodash.get "^4.4.2" lodash.groupby "^4.6.0" lodash.isempty "^4.4.0" lodash.isobject "^3.0.2" lodash.map "^4.6.0" lodash.merge "^4.6.2" lodash.omit "^4.5.0" lodash.orderby "^4.6.0" lodash.sortby "^4.7.0" lodash.uniq "^4.5.0" lodash.upperfirst "^4.3.1" lodash.values "^4.3.0" micromatch "4.0.2" needle "2.6.0" open "^7.0.3" ora "5.4.0" os-name "^3.0.0" pegjs "^0.10.0" promise-queue "^2.2.5" proxy-from-env "^1.0.0" rimraf "^2.6.3" semver "^6.0.0" snyk-config "4.0.0" snyk-cpp-plugin "2.2.1" snyk-docker-plugin "4.22.1" snyk-go-plugin "1.17.0" snyk-gradle-plugin "3.16.1" snyk-module "3.1.0" snyk-mvn-plugin "2.26.2" snyk-nodejs-lockfile-parser "1.35.0" snyk-nuget-plugin "1.22.0" snyk-php-plugin "1.9.2" snyk-policy "1.19.0" snyk-python-plugin "1.19.11" snyk-resolve "1.1.0" snyk-resolve-deps "4.7.2" snyk-sbt-plugin "2.11.3" snyk-try-require "1.3.1" source-map-support "^0.5.11" strip-ansi "^5.2.0" tar "^6.1.2" tempy "^1.0.1" update-notifier "^5.1.0" uuid "^8.3.2" wrap-ansi "^5.1.0" yaml "^1.10.2" socket.io-adapter@~2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-2.1.0.tgz#edc5dc36602f2985918d631c1399215e97a1b527" integrity sha512-+vDov/aTsLjViYTwS9fPy5pEtTkrbEKsw2M+oVSoFGw6OD1IpvlV1VPhUzNbofCQ8oyMbdYJqDtGdmHQK6TdPg== socket.io-parser@~4.0.3: version "4.0.4" resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-4.0.4.tgz#9ea21b0d61508d18196ef04a2c6b9ab630f4c2b0" integrity sha512-t+b0SS+IxG7Rxzda2EVvyBZbvFPBCjJoyHuE0P//7OAsN23GItzDRdWa6ALxZI/8R5ygK7jAR6t028/z+7295g== dependencies: "@types/component-emitter" "^1.2.10" component-emitter "~1.3.0" debug "~4.3.1" socket.io@^3.1.0: version "3.1.2" resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-3.1.2.tgz#06e27caa1c4fc9617547acfbb5da9bc1747da39a" integrity sha512-JubKZnTQ4Z8G4IZWtaAZSiRP3I/inpy8c/Bsx2jrwGrTbKeVU5xd6qkKMHpChYeM3dWZSO0QACiGK+obhBNwYw== dependencies: "@types/cookie" "^0.4.0" "@types/cors" "^2.8.8" "@types/node" ">=10.0.0" accepts "~1.3.4" base64id "~2.0.0" debug "~4.3.1" engine.io "~4.1.0" socket.io-adapter "~2.1.0" socket.io-parser "~4.0.3" socks-proxy-agent@^5.0.0: version "5.0.1" resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-5.0.1.tgz#032fb583048a29ebffec2e6a73fca0761f48177e" integrity sha512-vZdmnjb9a2Tz6WEQVIurybSwElwPxMZaIc7PzqbJTrezcKNznv6giT7J7tZDZ1BojVaa1jvO/UiUdhDVB0ACoQ== dependencies: agent-base "^6.0.2" debug "4" socks "^2.3.3" socks@^2.3.3: version "2.6.1" resolved "https://registry.yarnpkg.com/socks/-/socks-2.6.1.tgz#989e6534a07cf337deb1b1c94aaa44296520d30e" integrity sha512-kLQ9N5ucj8uIcxrDwjm0Jsqk06xdpBjGNQtpXy4Q8/QY2k+fY7nZH8CARy+hkbG+SGAovmzzuauCpBlb8FrnBA== dependencies: ip "^1.1.5" smart-buffer "^4.1.0" sonic-boom@^1.0.2: version "1.4.1" resolved "https://registry.yarnpkg.com/sonic-boom/-/sonic-boom-1.4.1.tgz#d35d6a74076624f12e6f917ade7b9d75e918f53e" integrity sha512-LRHh/A8tpW7ru89lrlkU4AszXt1dbwSjVWguGrmlxE7tawVmDBlI1PILMkXAxJTwqhgsEeTHzj36D5CmHgQmNg== dependencies: atomic-sleep "^1.0.0" flatstr "^1.0.12" sort-keys@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-2.0.0.tgz#658535584861ec97d730d6cf41822e1f56684128" integrity sha1-ZYU1WEhh7JfXMNbPQYIuH1ZoQSg= dependencies: is-plain-obj "^1.0.0" sort-keys@^4.0.0: version "4.2.0" resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-4.2.0.tgz#6b7638cee42c506fff8c1cecde7376d21315be18" integrity sha512-aUYIEU/UviqPgc8mHR6IW1EGxkAXpeRETYcrzg8cLAvUPZcpAlleSXHV2mY7G12GphSH6Gzv+4MMVSSkbdteHg== dependencies: is-plain-obj "^2.0.0" sort-object-keys@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/sort-object-keys/-/sort-object-keys-1.1.3.tgz#bff833fe85cab147b34742e45863453c1e190b45" integrity sha512-855pvK+VkU7PaKYPc+Jjnmt4EzejQHyhhF33q31qG8x7maDzkeFhAAThdCYay11CISO+qAMwjOBP+fPZe0IPyg== sort-package-json@~1.44.0: version "1.44.0" resolved "https://registry.yarnpkg.com/sort-package-json/-/sort-package-json-1.44.0.tgz#470330be868f8a524a4607b26f2a0233e93d8b6d" integrity sha512-u9GUZvpavUCXV5SbEqXu9FRbsJrYU6WM10r3zA0gymGPufK5X82MblCLh9GW9l46pXKEZvK+FA3eVTqC4oMp4A== dependencies: detect-indent "^6.0.0" detect-newline "3.1.0" git-hooks-list "1.0.3" globby "10.0.0" is-plain-obj "2.1.0" sort-object-keys "^1.1.3" source-list-map@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34" integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw== source-map-loader@0.2.4: version "0.2.4" resolved "https://registry.yarnpkg.com/source-map-loader/-/source-map-loader-0.2.4.tgz#c18b0dc6e23bf66f6792437557c569a11e072271" integrity sha512-OU6UJUty+i2JDpTItnizPrlpOIBLmQbWMuBg9q5bVtnHACqw1tn9nNwqJLbv0/00JjnJb/Ee5g5WS5vrRv7zIQ== dependencies: async "^2.5.0" loader-utils "^1.1.0" source-map-resolve@^0.5.0: version "0.5.3" resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.3.tgz#190866bece7553e1f8f267a2ee82c606b5509a1a" integrity sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw== dependencies: atob "^2.1.2" decode-uri-component "^0.2.0" resolve-url "^0.2.1" source-map-url "^0.4.0" urix "^0.1.0" source-map-resolve@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.6.0.tgz#3d9df87e236b53f16d01e58150fc7711138e5ed2" integrity sha512-KXBr9d/fO/bWo97NXsPIAW1bFSBOuCnjbNTBMO7N59hsv5i9yzRDfcYwwt0l04+VqnKC+EwzvJZIP/qkuMgR/w== dependencies: atob "^2.1.2" decode-uri-component "^0.2.0" source-map-support@^0.5.11, source-map-support@^0.5.16, source-map-support@^0.5.7, source-map-support@~0.5.12, source-map-support@~0.5.19: version "0.5.19" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61" integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw== dependencies: buffer-from "^1.0.0" source-map "^0.6.0" source-map-url@^0.4.0: version "0.4.1" resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.1.tgz#0af66605a745a5a2f91cf1bbf8a7afbc283dec56" integrity sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw== source-map@^0.5.6: version "0.5.7" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== source-map@~0.7.2: version "0.7.3" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== spdx-correct@^3.0.0: version "3.1.1" resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9" integrity sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w== dependencies: spdx-expression-parse "^3.0.0" spdx-license-ids "^3.0.0" spdx-exceptions@^2.1.0: version "2.3.0" resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz#3f28ce1a77a00372683eade4a433183527a2163d" integrity sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A== spdx-expression-parse@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz#cf70f50482eefdc98e3ce0a6833e4a53ceeba679" integrity sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q== dependencies: spdx-exceptions "^2.1.0" spdx-license-ids "^3.0.0" spdx-license-ids@^3.0.0: version "3.0.9" resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.9.tgz#8a595135def9592bda69709474f1cbeea7c2467f" integrity sha512-Ki212dKK4ogX+xDo4CtOZBVIwhsKBEfsEEcwmJfLQzirgc2jIWdzg40Unxz/HzEUqM1WFzVlQSMF9kZZ2HboLQ== split-ca@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/split-ca/-/split-ca-1.0.1.tgz#6c83aff3692fa61256e0cd197e05e9de157691a6" integrity sha1-bIOv82kvphJW4M0ZfgXp3hV2kaY= split-on-first@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/split-on-first/-/split-on-first-1.1.0.tgz#f610afeee3b12bce1d0c30425e76398b78249a5f" integrity sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw== split-string@^3.0.1, split-string@^3.0.2: version "3.1.0" resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" integrity sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw== dependencies: extend-shallow "^3.0.0" split2@^3.0.0: version "3.2.2" resolved "https://registry.yarnpkg.com/split2/-/split2-3.2.2.tgz#bf2cf2a37d838312c249c89206fd7a17dd12365f" integrity sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg== dependencies: readable-stream "^3.0.0" split@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/split/-/split-1.0.1.tgz#605bd9be303aa59fb35f9229fbea0ddec9ea07d9" integrity sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg== dependencies: through "2" sprintf-js@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.2.tgz#da1765262bf8c0f571749f2ad6c26300207ae673" integrity sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug== sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= ssh2-streams@~0.4.10: version "0.4.10" resolved "https://registry.yarnpkg.com/ssh2-streams/-/ssh2-streams-0.4.10.tgz#48ef7e8a0e39d8f2921c30521d56dacb31d23a34" integrity sha512-8pnlMjvnIZJvmTzUIIA5nT4jr2ZWNNVHwyXfMGdRJbug9TpI3kd99ffglgfSWqujVv/0gxwMsDn9j9RVst8yhQ== dependencies: asn1 "~0.2.0" bcrypt-pbkdf "^1.0.2" streamsearch "~0.1.2" ssh2@^0.8.7: version "0.8.9" resolved "https://registry.yarnpkg.com/ssh2/-/ssh2-0.8.9.tgz#54da3a6c4ba3daf0d8477a538a481326091815f3" integrity sha512-GmoNPxWDMkVpMFa9LVVzQZHF6EW3WKmBwL+4/GeILf2hFmix5Isxm7Amamo8o7bHiU0tC+wXsGcUXOxp8ChPaw== dependencies: ssh2-streams "~0.4.10" sshpk@^1.7.0: version "1.16.1" resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877" integrity sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg== dependencies: asn1 "~0.2.3" assert-plus "^1.0.0" bcrypt-pbkdf "^1.0.0" dashdash "^1.12.0" ecc-jsbn "~0.1.1" getpass "^0.1.1" jsbn "~0.1.0" safer-buffer "^2.0.2" tweetnacl "~0.14.0" ssri@^5.2.4: version "5.3.0" resolved "https://registry.yarnpkg.com/ssri/-/ssri-5.3.0.tgz#ba3872c9c6d33a0704a7d71ff045e5ec48999d06" integrity sha512-XRSIPqLij52MtgoQavH/x/dU1qVKtWUAAZeOHsR9c2Ddi4XerFy3mc1alf+dLJKl9EUIm/Ht+EowFkTUOA6GAQ== dependencies: safe-buffer "^5.1.1" ssri@^6.0.1: version "6.0.2" resolved "https://registry.yarnpkg.com/ssri/-/ssri-6.0.2.tgz#157939134f20464e7301ddba3e90ffa8f7728ac5" integrity sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q== dependencies: figgy-pudding "^3.5.1" ssri@^8.0.0, ssri@^8.0.1: version "8.0.1" resolved "https://registry.yarnpkg.com/ssri/-/ssri-8.0.1.tgz#638e4e439e2ffbd2cd289776d5ca457c4f51a2af" integrity sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ== dependencies: minipass "^3.1.1" stable@^0.1.8: version "0.1.8" resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf" integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w== static-extend@^0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" integrity sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY= dependencies: define-property "^0.2.5" object-copy "^0.1.0" "statuses@>= 1.5.0 < 2", statuses@~1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= stealthy-require@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/stealthy-require/-/stealthy-require-1.1.1.tgz#35b09875b4ff49f26a777e509b3090a3226bf24b" integrity sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks= steno@^0.4.1: version "0.4.4" resolved "https://registry.yarnpkg.com/steno/-/steno-0.4.4.tgz#071105bdfc286e6615c0403c27e9d7b5dcb855cb" integrity sha1-BxEFvfwobmYVwEA8J+nXtdy4Vcs= dependencies: graceful-fs "^4.1.3" stream-browserify@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.2.tgz#87521d38a44aa7ee91ce1cd2a47df0cb49dd660b" integrity sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg== dependencies: inherits "~2.0.1" readable-stream "^2.0.2" stream-buffers@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/stream-buffers/-/stream-buffers-3.0.2.tgz#5249005a8d5c2d00b3a32e6e0a6ea209dc4f3521" integrity sha512-DQi1h8VEBA/lURbSwFtEHnSTb9s2/pwLEaFuNhXwy1Dx3Sa0lOuYT2yNUr4/j2fs8oCAMANtrZ5OrPZtyVs3MQ== stream-each@^1.1.0: version "1.2.3" resolved "https://registry.yarnpkg.com/stream-each/-/stream-each-1.2.3.tgz#ebe27a0c389b04fbcc233642952e10731afa9bae" integrity sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw== dependencies: end-of-stream "^1.1.0" stream-shift "^1.0.0" stream-http@^2.7.2: version "2.8.3" resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.8.3.tgz#b2d242469288a5a27ec4fe8933acf623de6514fc" integrity sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw== dependencies: builtin-status-codes "^3.0.0" inherits "^2.0.1" readable-stream "^2.3.6" to-arraybuffer "^1.0.0" xtend "^4.0.0" stream-shift@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.1.tgz#d7088281559ab2778424279b0877da3c392d5a3d" integrity sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ== stream-to-array@~2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/stream-to-array/-/stream-to-array-2.3.0.tgz#bbf6b39f5f43ec30bc71babcb37557acecf34353" integrity sha1-u/azn19D7DC8cbq8s3VXrOzzQ1M= dependencies: any-promise "^1.1.0" stream-to-promise@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/stream-to-promise/-/stream-to-promise-2.2.0.tgz#b1edb2e1c8cb11289d1b503c08d3f2aef51e650f" integrity sha1-se2y4cjLESidG1A8CNPyrvUeZQ8= dependencies: any-promise "~1.3.0" end-of-stream "~1.1.0" stream-to-array "~2.3.0" streamroller@^2.2.4: version "2.2.4" resolved "https://registry.yarnpkg.com/streamroller/-/streamroller-2.2.4.tgz#c198ced42db94086a6193608187ce80a5f2b0e53" integrity sha512-OG79qm3AujAM9ImoqgWEY1xG4HX+Lw+yY6qZj9R1K2mhF5bEmQ849wvrb+4vt4jLMLzwXttJlQbOdPOQVRv7DQ== dependencies: date-format "^2.1.0" debug "^4.1.1" fs-extra "^8.1.0" streamsearch@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-0.1.2.tgz#808b9d0e56fc273d809ba57338e929919a1a9f1a" integrity sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo= strict-uri-encode@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz#b9c7330c7042862f6b142dc274bbcc5866ce3546" integrity sha1-ucczDHBChi9rFC3CdLvMWGbONUY= string-argv@0.3.1, string-argv@~0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.1.tgz#95e2fbec0427ae19184935f816d74aaa4c5c19da" integrity sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg== string-hash@^1.1.1: version "1.1.3" resolved "https://registry.yarnpkg.com/string-hash/-/string-hash-1.1.3.tgz#e8aafc0ac1855b4666929ed7dd1275df5d6c811b" integrity sha1-6Kr8CsGFW0Zmkp7X3RJ1311sgRs= string-width@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" integrity sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M= dependencies: code-point-at "^1.0.0" is-fullwidth-code-point "^1.0.0" strip-ansi "^3.0.0" "string-width@^1.0.2 || 2": version "2.1.1" resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== dependencies: is-fullwidth-code-point "^2.0.0" strip-ansi "^4.0.0" string-width@^3.0.0, string-width@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w== dependencies: emoji-regex "^7.0.1" is-fullwidth-code-point "^2.0.0" strip-ansi "^5.1.0" string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0: version "4.2.2" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.2.tgz#dafd4f9559a7585cfba529c6a0a4f73488ebd4c5" integrity sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA== dependencies: emoji-regex "^8.0.0" is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.0" string.prototype.trimend@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz#e75ae90c2942c63504686c18b287b4a0b1a45f80" integrity sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A== dependencies: call-bind "^1.0.2" define-properties "^1.1.3" string.prototype.trimstart@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz#b36399af4ab2999b4c9c648bd7a3fb2bb26feeed" integrity sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw== dependencies: call-bind "^1.0.2" define-properties "^1.1.3" string_decoder@^1.0.0, string_decoder@^1.1.1: version "1.3.0" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== dependencies: safe-buffer "~5.2.0" string_decoder@~1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== dependencies: safe-buffer "~5.1.0" stringify-object@^3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/stringify-object/-/stringify-object-3.3.0.tgz#703065aefca19300d3ce88af4f5b3956d7556629" integrity sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw== dependencies: get-own-enumerable-property-symbols "^3.0.0" is-obj "^1.0.1" is-regexp "^1.0.0" strip-ansi@6.0.0, strip-ansi@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532" integrity sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w== dependencies: ansi-regex "^5.0.0" strip-ansi@^3.0.0, strip-ansi@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= dependencies: ansi-regex "^2.0.0" strip-ansi@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= dependencies: ansi-regex "^3.0.0" strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== dependencies: ansi-regex "^4.1.0" strip-bom@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM= strip-bom@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== strip-eof@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8= strip-final-newline@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== strip-indent@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-3.0.0.tgz#c32e1cee940b6b3432c771bc2c54bcce73cd3001" integrity sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ== dependencies: min-indent "^1.0.0" strip-json-comments@3.1.1, strip-json-comments@^3.1.0, strip-json-comments@^3.1.1, strip-json-comments@~3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== strip-json-comments@~2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= strong-log-transformer@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/strong-log-transformer/-/strong-log-transformer-2.1.0.tgz#0f5ed78d325e0421ac6f90f7f10e691d6ae3ae10" integrity sha512-B3Hgul+z0L9a236FAUC9iZsL+nVHgoCJnqCbN588DjYxvGXaXaaFbfmQ/JhvKjZwsOukuR72XbHv71Qkug0HxA== dependencies: duplexer "^0.1.1" minimist "^1.2.0" through "^2.3.4" style-inject@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/style-inject/-/style-inject-0.3.0.tgz#d21c477affec91811cc82355832a700d22bf8dd3" integrity sha512-IezA2qp+vcdlhJaVm5SOdPPTUu0FCEqfNSli2vRuSIBbu5Nq5UvygTk/VzeCqfLz2Atj3dVII5QBKGZRZ0edzw== style-loader@^1.0.2: version "1.3.0" resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-1.3.0.tgz#828b4a3b3b7e7aa5847ce7bae9e874512114249e" integrity sha512-V7TCORko8rs9rIqkSrlMfkqA63DfoGBBJmK1kKGCcSi+BWb4cqz0SRsnp4l6rU5iwOEd0/2ePv68SV22VXon4Q== dependencies: loader-utils "^2.0.0" schema-utils "^2.7.0" stylehacks@^4.0.0: version "4.0.3" resolved "https://registry.yarnpkg.com/stylehacks/-/stylehacks-4.0.3.tgz#6718fcaf4d1e07d8a1318690881e8d96726a71d5" integrity sha512-7GlLk9JwlElY4Y6a/rmbH2MhVlTyVmiJd1PfTCqFaIBEGMYNsrO/v3SeGTdhBThLg4Z+NbOk/qFMwCa+J+3p/g== dependencies: browserslist "^4.0.0" postcss "^7.0.0" postcss-selector-parser "^3.0.0" supports-color@8.1.1: version "8.1.1" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== dependencies: has-flag "^4.0.0" supports-color@^5.3.0: version "5.5.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== dependencies: has-flag "^3.0.0" supports-color@^6.1.0: version "6.1.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3" integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ== dependencies: has-flag "^3.0.0" supports-color@^7.1.0: version "7.2.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== dependencies: has-flag "^4.0.0" svgo@^1.0.0: version "1.3.2" resolved "https://registry.yarnpkg.com/svgo/-/svgo-1.3.2.tgz#b6dc511c063346c9e415b81e43401145b96d4167" integrity sha512-yhy/sQYxR5BkC98CY7o31VGsg014AKLEPxdfhora76l36hD9Rdy5NZA/Ocn6yayNPgSamYdtX2rFJdcv07AYVw== dependencies: chalk "^2.4.1" coa "^2.0.2" css-select "^2.0.0" css-select-base-adapter "^0.1.1" css-tree "1.0.0-alpha.37" csso "^4.0.2" js-yaml "^3.13.1" mkdirp "~0.5.1" object.values "^1.1.0" sax "~1.2.4" stable "^0.1.8" unquote "~1.1.1" util.promisify "~1.0.0" symbol-tree@^3.2.2: version "3.2.4" resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== table@^5.2.3: version "5.4.6" resolved "https://registry.yarnpkg.com/table/-/table-5.4.6.tgz#1292d19500ce3f86053b05f0e8e7e4a3bb21079e" integrity sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug== dependencies: ajv "^6.10.2" lodash "^4.17.14" slice-ansi "^2.1.0" string-width "^3.0.0" tapable@^1.0.0, tapable@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2" integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA== tar-stream@^2.0.1, tar-stream@^2.1.0, tar-stream@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287" integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ== dependencies: bl "^4.0.3" end-of-stream "^1.4.1" fs-constants "^1.0.0" inherits "^2.0.3" readable-stream "^3.1.1" tar@^4.4.12: version "4.4.19" resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.19.tgz#2e4d7263df26f2b914dee10c825ab132123742f3" integrity sha512-a20gEsvHnWe0ygBY8JbxoM4w3SJdhc7ZAuxkLqh+nvNQN2IOt0B5lLgM490X5Hl8FF0dl0tOf2ewFYAlIFgzVA== dependencies: chownr "^1.1.4" fs-minipass "^1.2.7" minipass "^2.9.0" minizlib "^1.3.3" mkdirp "^0.5.5" safe-buffer "^5.2.1" yallist "^3.1.1" tar@^6.0.2, tar@^6.1.0, tar@^6.1.2: version "6.1.6" resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.6.tgz#c23d797b0a1efe5d479b1490805c5443f3560c5d" integrity sha512-oaWyu5dQbHaYcyZCTfyPpC+VmI62/OM2RTUYavTk1MDr1cwW5Boi3baeYQKiZbY2uSQJGr+iMOzb/JFxLrft+g== dependencies: chownr "^2.0.0" fs-minipass "^2.0.0" minipass "^3.0.0" minizlib "^2.1.1" mkdirp "^1.0.3" yallist "^4.0.0" temp-dir@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/temp-dir/-/temp-dir-1.0.0.tgz#0a7c0ea26d3a39afa7e0ebea9c1fc0bc4daa011d" integrity sha1-CnwOom06Oa+n4OvqnB/AvE2qAR0= temp-dir@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/temp-dir/-/temp-dir-2.0.0.tgz#bde92b05bdfeb1516e804c9c00ad45177f31321e" integrity sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg== temp-write@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/temp-write/-/temp-write-4.0.0.tgz#cd2e0825fc826ae72d201dc26eef3bf7e6fc9320" integrity sha512-HIeWmj77uOOHb0QX7siN3OtwV3CTntquin6TNVg6SHOqCP3hYKmox90eeFOGaY1MqJ9WYDDjkyZrW6qS5AWpbw== dependencies: graceful-fs "^4.1.15" is-stream "^2.0.0" make-dir "^3.0.0" temp-dir "^1.0.0" uuid "^3.3.2" tempy@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/tempy/-/tempy-1.0.1.tgz#30fe901fd869cfb36ee2bd999805aa72fbb035de" integrity sha512-biM9brNqxSc04Ee71hzFbryD11nX7VPhQQY32AdDmjFvodsRFz/3ufeoTZ6uYkRFfGo188tENcASNs3vTdsM0w== dependencies: del "^6.0.0" is-stream "^2.0.0" temp-dir "^2.0.0" type-fest "^0.16.0" unique-string "^2.0.0" terser-webpack-plugin@^1.4.3: version "1.4.5" resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-1.4.5.tgz#a217aefaea330e734ffacb6120ec1fa312d6040b" integrity sha512-04Rfe496lN8EYruwi6oPQkG0vo8C+HT49X687FZnpPF0qMAIHONI6HEXYPKDOE8e5HjXTyKfqRd/agHtH0kOtw== dependencies: cacache "^12.0.2" find-cache-dir "^2.1.0" is-wsl "^1.1.0" schema-utils "^1.0.0" serialize-javascript "^4.0.0" source-map "^0.6.1" terser "^4.1.2" webpack-sources "^1.4.0" worker-farm "^1.7.0" terser@^4.1.2: version "4.8.0" resolved "https://registry.yarnpkg.com/terser/-/terser-4.8.0.tgz#63056343d7c70bb29f3af665865a46fe03a0df17" integrity sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw== dependencies: commander "^2.20.0" source-map "~0.6.1" source-map-support "~0.5.12" terser@^5.7.1: version "5.7.1" resolved "https://registry.yarnpkg.com/terser/-/terser-5.7.1.tgz#2dc7a61009b66bb638305cb2a824763b116bf784" integrity sha512-b3e+d5JbHAe/JSjwsC3Zn55wsBIM7AsHLjKxT31kGCldgbpFePaFo+PiddtO6uwRZWRw7sPXmAN8dTW61xmnSg== dependencies: commander "^2.20.0" source-map "~0.7.2" source-map-support "~0.5.19" text-extensions@^1.0.0: version "1.9.0" resolved "https://registry.yarnpkg.com/text-extensions/-/text-extensions-1.9.0.tgz#1853e45fee39c945ce6f6c36b2d659b5aabc2a26" integrity sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ== text-table@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= then-fs@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/then-fs/-/then-fs-2.0.0.tgz#72f792dd9d31705a91ae19ebfcf8b3f968c81da2" integrity sha1-cveS3Z0xcFqRrhnr/Piz+WjIHaI= dependencies: promise ">=3.2 <8" through2@^2.0.0, through2@^2.0.3: version "2.0.5" resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd" integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ== dependencies: readable-stream "~2.3.6" xtend "~4.0.1" through2@^4.0.0: version "4.0.2" resolved "https://registry.yarnpkg.com/through2/-/through2-4.0.2.tgz#a7ce3ac2a7a8b0b966c80e7c49f0484c3b239764" integrity sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw== dependencies: readable-stream "3" through@2, "through@>=2.2.7 <3", through@^2.3.4, through@^2.3.6, through@^2.3.8: version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= timers-browserify@^2.0.4: version "2.0.12" resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.12.tgz#44a45c11fbf407f34f97bccd1577c652361b00ee" integrity sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ== dependencies: setimmediate "^1.0.4" timers-ext@^0.1.7: version "0.1.7" resolved "https://registry.yarnpkg.com/timers-ext/-/timers-ext-0.1.7.tgz#6f57ad8578e07a3fb9f91d9387d65647555e25c6" integrity sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ== dependencies: es5-ext "~0.10.46" next-tick "1" timsort@^0.3.0, timsort@~0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4" integrity sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q= tmp@0.2.1, tmp@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.1.tgz#8457fc3037dcf4719c251367a1af6500ee1ccf14" integrity sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ== dependencies: rimraf "^3.0.0" tmp@^0.0.33: version "0.0.33" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== dependencies: os-tmpdir "~1.0.2" tmp@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.1.0.tgz#ee434a4e22543082e294ba6201dcc6eafefa2877" integrity sha512-J7Z2K08jbGcdA1kkQpJSqLF6T0tdQqpR2pnSUXsIchbPdTI9v3e85cLW0d6WDhwuAleOV71j2xWs8qMPfK7nKw== dependencies: rimraf "^2.6.3" to-arraybuffer@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43" integrity sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M= to-object-path@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af" integrity sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68= dependencies: kind-of "^3.0.2" to-readable-stream@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/to-readable-stream/-/to-readable-stream-1.0.0.tgz#ce0aa0c2f3df6adf852efb404a783e77c0475771" integrity sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q== to-regex-range@^2.1.0: version "2.1.1" resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38" integrity sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg= dependencies: is-number "^3.0.0" repeat-string "^1.6.1" to-regex-range@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== dependencies: is-number "^7.0.0" to-regex@^3.0.1, to-regex@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce" integrity sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw== dependencies: define-property "^2.0.2" extend-shallow "^3.0.2" regex-not "^1.0.2" safe-regex "^1.1.0" toidentifier@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw== toml@3.0.0, toml@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/toml/-/toml-3.0.0.tgz#342160f1af1904ec9d204d03a5d61222d762c5ee" integrity sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w== tough-cookie@^2.3.3, tough-cookie@~2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== dependencies: psl "^1.1.28" punycode "^2.1.1" tough-cookie@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-3.0.1.tgz#9df4f57e739c26930a018184887f4adb7dca73b2" integrity sha512-yQyJ0u4pZsv9D4clxO69OEjLWYw+jbgspjTue4lTQZLfV0c5l1VmK2y1JK8E9ahdpltPOaAThPcp5nKPUgSnsg== dependencies: ip-regex "^2.1.0" psl "^1.1.28" punycode "^2.1.1" tough-cookie@~2.4.3: version "2.4.3" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781" integrity sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ== dependencies: psl "^1.1.24" punycode "^1.4.1" tr46@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/tr46/-/tr46-1.0.1.tgz#a8b13fd6bfd2489519674ccde55ba3693b706d09" integrity sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk= dependencies: punycode "^2.1.0" tr46@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/tr46/-/tr46-2.1.0.tgz#fa87aa81ca5d5941da8cbf1f9b749dc969a4e240" integrity sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw== dependencies: punycode "^2.1.1" tree-kill@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc" integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A== treeify@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/treeify/-/treeify-1.1.0.tgz#4e31c6a463accd0943879f30667c4fdaff411bb8" integrity sha512-1m4RA7xVAJrSGrrXGs0L3YTwyvBs2S8PbRHaLZAkFw7JR8oIFwYtysxlBZhYIa7xSyiYJKZ3iGrrk55cGA3i9A== trim-newlines@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-3.0.1.tgz#260a5d962d8b752425b32f3a7db0dcacd176c144" integrity sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw== trim-off-newlines@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/trim-off-newlines/-/trim-off-newlines-1.0.1.tgz#9f9ba9d9efa8764c387698bcbfeb2c848f11adb3" integrity sha1-n5up2e+odkw4dpi8v+sshI8RrbM= tslib@1.11.1: version "1.11.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.11.1.tgz#eb15d128827fbee2841549e171f45ed338ac7e35" integrity sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA== tslib@^1, tslib@^1.10.0, tslib@^1.11.2, tslib@^1.13.0, tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3: version "1.14.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== tslib@^2.0.0, tslib@^2.1.0, tslib@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.0.tgz#803b8cdab3e12ba581a4ca41c8839bbb0dacb09e" integrity sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg== tsscmp@1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/tsscmp/-/tsscmp-1.0.6.tgz#85b99583ac3589ec4bfef825b5000aa911d605eb" integrity sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA== tsutils@^3.17.1: version "3.21.0" resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA== dependencies: tslib "^1.8.1" tty-browserify@0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6" integrity sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY= tunnel-agent@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" integrity sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0= dependencies: safe-buffer "^5.0.1" tunnel@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/tunnel/-/tunnel-0.0.6.tgz#72f1314b34a5b192db012324df2cc587ca47f92c" integrity sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg== tweetnacl@^0.14.3, tweetnacl@~0.14.0: version "0.14.5" resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= typanion@^3.3.1: version "3.3.2" resolved "https://registry.yarnpkg.com/typanion/-/typanion-3.3.2.tgz#c31f3b2afb6e8ae74dbd3f96d5b1d8f9745e483e" integrity sha512-m3v3wtFc6R0wtl0RpEn11bKXIOjS1zch5gmx0zg2G5qfGQ3A9TVZRMSL43O5eFuGXsrgzyvDcGRmSXGP5UqpDQ== type-check@^0.4.0, type-check@~0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== dependencies: prelude-ls "^1.2.1" type-check@~0.3.2: version "0.3.2" resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" integrity sha1-WITKtRLPHTVeP7eE8wgEsrUg23I= dependencies: prelude-ls "~1.1.2" type-detect@^4.0.0, type-detect@^4.0.5: version "4.0.8" resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== type-fest@^0.13.1: version "0.13.1" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.13.1.tgz#0172cb5bce80b0bd542ea348db50c7e21834d934" integrity sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg== type-fest@^0.16.0: version "0.16.0" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.16.0.tgz#3240b891a78b0deae910dbeb86553e552a148860" integrity sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg== type-fest@^0.18.0: version "0.18.1" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.18.1.tgz#db4bc151a4a2cf4eebf9add5db75508db6cc841f" integrity sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw== type-fest@^0.20.2: version "0.20.2" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== type-fest@^0.21.3: version "0.21.3" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== type-fest@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.4.1.tgz#8bdf77743385d8a4f13ba95f610f5ccd68c728f8" integrity sha512-IwzA/LSfD2vC1/YDYMv/zHP4rDF1usCwllsDpbolT3D4fUepIO7f9K70jjmUewU/LmGUKJcwcVtDCpnKk4BPMw== type-fest@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.6.0.tgz#8d2a2370d3df886eb5c90ada1c5bf6188acf838b" integrity sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg== type-fest@^0.8.1: version "0.8.1" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== type-is@~1.6.17, type-is@~1.6.18: version "1.6.18" resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== dependencies: media-typer "0.3.0" mime-types "~2.1.24" type@^1.0.1: version "1.2.0" resolved "https://registry.yarnpkg.com/type/-/type-1.2.0.tgz#848dd7698dafa3e54a6c479e759c4bc3f18847a0" integrity sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg== type@^2.0.0: version "2.5.0" resolved "https://registry.yarnpkg.com/type/-/type-2.5.0.tgz#0a2e78c2e77907b252abe5f298c1b01c63f0db3d" integrity sha512-180WMDQaIMm3+7hGXWf12GtdniDEy7nYcyFMKJn/eZz/6tSLXrUN9V0wKSbMjej0I1WHWbpREDEKHtqPQa9NNw== typedarray-to-buffer@^3.1.5: version "3.1.5" resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q== dependencies: is-typedarray "^1.0.0" typedarray@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= typedoc-default-themes@^0.6.3: version "0.6.3" resolved "https://registry.yarnpkg.com/typedoc-default-themes/-/typedoc-default-themes-0.6.3.tgz#c214ce5bbcc6045558448a8fd422b90e3e9b6782" integrity sha512-rouf0TcIA4M2nOQFfC7Zp4NEwoYiEX4vX/ZtudJWU9IHA29MPC+PPgSXYLPESkUo7FuB//GxigO3mk9Qe1xp3Q== dependencies: backbone "^1.4.0" jquery "^3.4.1" lunr "^2.3.8" underscore "^1.9.1" typedoc@~0.15.0: version "0.15.8" resolved "https://registry.yarnpkg.com/typedoc/-/typedoc-0.15.8.tgz#d83195445a718d173e0d5c73b5581052cb47d4d9" integrity sha512-a0zypcvfIFsS7Gqpf2MkC1+jNND3K1Om38pbDdy/gYWX01NuJZhC5+O0HkIp0oRIZOo7PWrA5+fC24zkANY28Q== dependencies: "@types/minimatch" "3.0.3" fs-extra "^8.1.0" handlebars "^4.7.0" highlight.js "^9.17.1" lodash "^4.17.15" marked "^0.8.0" minimatch "^3.0.0" progress "^2.0.3" shelljs "^0.8.3" typedoc-default-themes "^0.6.3" typescript "3.7.x" typescript@3.7.x: version "3.7.7" resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.7.tgz#c931733e2ec10dda56b855b379cc488a72a81199" integrity sha512-MmQdgo/XenfZPvVLtKZOq9jQQvzaUAUpcKW8Z43x9B2fOm4S5g//tPtMweZUIP+SoBqrVPEIm+dJeQ9dfO0QdA== typescript@^3.8.0-dev.20200111: version "3.9.10" resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.10.tgz#70f3910ac7a51ed6bef79da7800690b19bf778b8" integrity sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q== typescript@~3.6.0: version "3.6.5" resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.6.5.tgz#dae20114a7b4ff4bd642db9c8c699f2953e8bbdb" integrity sha512-BEjlc0Z06ORZKbtcxGrIvvwYs5hAnuo6TKdNFL55frVDlB+na3z5bsLhFaIxmT+dPWgBIjMo6aNnTOgHHmHgiQ== typescript@~4.1.3: version "4.1.6" resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.1.6.tgz#1becd85d77567c3c741172339e93ce2e69932138" integrity sha512-pxnwLxeb/Z5SP80JDRzVjh58KsM6jZHRAOtTpS7sXLS4ogXNKC9ANxHHZqLLeVHZN35jCtI4JdmLLbLiC1kBow== typescript@~4.3.5: version "4.3.5" resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.3.5.tgz#4d1c37cc16e893973c45a06886b7113234f119f4" integrity sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA== ua-parser-js@^0.7.28: version "0.7.28" resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.28.tgz#8ba04e653f35ce210239c64661685bf9121dec31" integrity sha512-6Gurc1n//gjp9eQNXjD9O3M/sMwVtN5S8Lv9bvOYBfKfDNiIIhqiyi01vMBO45u4zkDE420w/e0se7Vs+sIg+g== uglify-js@^3.1.4: version "3.14.1" resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.14.1.tgz#e2cb9fe34db9cb4cf7e35d1d26dfea28e09a7d06" integrity sha512-JhS3hmcVaXlp/xSo3PKY5R0JqKs5M3IV+exdLHW99qKvKivPO4Z8qbej6mte17SOPqAOVMjt/XGgWacnFSzM3g== uid-number@0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81" integrity sha1-DqEOgDXo61uOREnwbaHHMGY7qoE= umask@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/umask/-/umask-1.1.0.tgz#f29cebf01df517912bb58ff9c4e50fde8e33320d" integrity sha1-8pzr8B31F5ErtY/5xOUP3o4zMg0= unbox-primitive@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.1.tgz#085e215625ec3162574dc8859abee78a59b14471" integrity sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw== dependencies: function-bind "^1.1.1" has-bigints "^1.0.1" has-symbols "^1.0.2" which-boxed-primitive "^1.0.2" underscore@>=1.8.3, underscore@^1.9.1: version "1.13.1" resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.13.1.tgz#0c1c6bd2df54b6b69f2314066d65b6cde6fcf9d1" integrity sha512-hzSoAVtJF+3ZtiFX0VgfFPHEDRm7Y/QPjGyNo4TVdnDTdft3tr8hEkD25a1jC+TjTuE7tkHGKkhwCgs9dgBB2g== union-value@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847" integrity sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg== dependencies: arr-union "^3.1.0" get-value "^2.0.6" is-extendable "^0.1.1" set-value "^2.0.1" uniq@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff" integrity sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8= uniqs@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/uniqs/-/uniqs-2.0.0.tgz#ffede4b36b25290696e6e165d4a59edb998e6b02" integrity sha1-/+3ks2slKQaW5uFl1KWe25mOawI= unique-filename@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-1.1.1.tgz#1d69769369ada0583103a1e6ae87681b56573230" integrity sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ== dependencies: unique-slug "^2.0.0" unique-slug@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-2.0.2.tgz#baabce91083fc64e945b0f3ad613e264f7cd4e6c" integrity sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w== dependencies: imurmurhash "^0.1.4" unique-string@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-2.0.0.tgz#39c6451f81afb2749de2b233e3f7c5e8843bd89d" integrity sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg== dependencies: crypto-random-string "^2.0.0" universal-user-agent@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/universal-user-agent/-/universal-user-agent-6.0.0.tgz#3381f8503b251c0d9cd21bc1de939ec9df5480ee" integrity sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w== universalify@^0.1.0: version "0.1.2" resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== universalify@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ== unix-crypt-td-js@1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/unix-crypt-td-js/-/unix-crypt-td-js-1.1.4.tgz#4912dfad1c8aeb7d20fa0a39e4c31918c1d5d5dd" integrity sha512-8rMeVYWSIyccIJscb9NdCfZKSRBKYTeVnwmiRYT2ulE3qd1RaDQ0xQDP+rI3ccIWbhu/zuo5cgN8z73belNZgw== unpipe@1.0.0, unpipe@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= unquote@~1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/unquote/-/unquote-1.1.1.tgz#8fded7324ec6e88a0ff8b905e7c098cdc086d544" integrity sha1-j97XMk7G6IoP+LkF58CYzcCG1UQ= unset-value@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unset-value/-/unset-value-1.0.0.tgz#8376873f7d2335179ffb1e6fc3a8ed0dfc8ab559" integrity sha1-g3aHP30jNRef+x5vw6jtDfyKtVk= dependencies: has-value "^0.3.1" isobject "^3.0.0" upath@2.0.1, upath@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/upath/-/upath-2.0.1.tgz#50c73dea68d6f6b990f51d279ce6081665d61a8b" integrity sha512-1uEe95xksV1O0CYKXo8vQvN1JEbtJp7lb7C5U9HMsIp6IVwntkH/oNUzyVNQSd4S1sYk2FpSSW44FqMc8qee5w== upath@^1.1.1: version "1.2.0" resolved "https://registry.yarnpkg.com/upath/-/upath-1.2.0.tgz#8f66dbcd55a883acdae4408af8b035a5044c1894" integrity sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg== update-notifier@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-5.1.0.tgz#4ab0d7c7f36a231dd7316cf7729313f0214d9ad9" integrity sha512-ItnICHbeMh9GqUy31hFPrD1kcuZ3rpxDZbf4KUDavXwS0bW5m7SLbDQpGX3UYr072cbrF5hFUs3r5tUsPwjfHw== dependencies: boxen "^5.0.0" chalk "^4.1.0" configstore "^5.0.1" has-yarn "^2.1.0" import-lazy "^2.1.0" is-ci "^2.0.0" is-installed-globally "^0.4.0" is-npm "^5.0.0" is-yarn-global "^0.3.0" latest-version "^5.1.0" pupa "^2.1.1" semver "^7.3.4" semver-diff "^3.1.1" xdg-basedir "^4.0.0" uri-js@^4.2.2: version "4.4.1" resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== dependencies: punycode "^2.1.0" urix@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI= url-parse-lax@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-3.0.0.tgz#16b5cafc07dbe3676c1b1999177823d6503acb0c" integrity sha1-FrXK/Afb42dsGxmZF3gj1lA6yww= dependencies: prepend-http "^2.0.0" url@^0.11.0: version "0.11.0" resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1" integrity sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE= dependencies: punycode "1.3.2" querystring "0.2.0" use@^3.1.0: version "3.1.1" resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== utf-8-validate@^5.0.2: version "5.0.5" resolved "https://registry.yarnpkg.com/utf-8-validate/-/utf-8-validate-5.0.5.tgz#dd32c2e82c72002dc9f02eb67ba6761f43456ca1" integrity sha512-+pnxRYsS/axEpkrrEpzYfNZGXp0IjC/9RIxwM5gntY4Koi8SHmUGSfxfWqxZdRxrtaoVstuOzUp/rbs3JSPELQ== dependencies: node-gyp-build "^4.2.0" utf8@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/utf8/-/utf8-3.0.0.tgz#f052eed1364d696e769ef058b183df88c87f69d1" integrity sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ== util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= util-promisify@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/util-promisify/-/util-promisify-2.1.0.tgz#3c2236476c4d32c5ff3c47002add7c13b9a82a53" integrity sha1-PCI2R2xNMsX/PEcAKt18E7moKlM= dependencies: object.getownpropertydescriptors "^2.0.3" util.promisify@~1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/util.promisify/-/util.promisify-1.0.1.tgz#6baf7774b80eeb0f7520d8b81d07982a59abbaee" integrity sha512-g9JpC/3He3bm38zsLupWryXHoEcS22YHthuPQSJdMy6KNrzIRzWqcsHzD/WUnqe45whVou4VIsPew37DoXWNrA== dependencies: define-properties "^1.1.3" es-abstract "^1.17.2" has-symbols "^1.0.1" object.getownpropertydescriptors "^2.1.0" util@0.10.3: version "0.10.3" resolved "https://registry.yarnpkg.com/util/-/util-0.10.3.tgz#7afb1afe50805246489e3db7fe0ed379336ac0f9" integrity sha1-evsa/lCAUkZInj23/g7TeTNqwPk= dependencies: inherits "2.0.1" util@^0.11.0: version "0.11.1" resolved "https://registry.yarnpkg.com/util/-/util-0.11.1.tgz#3236733720ec64bb27f6e26f421aaa2e1b588d61" integrity sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ== dependencies: inherits "2.0.3" utils-merge@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= uuid@^3.3.2: version "3.4.0" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== uuid@^8.2.0, uuid@^8.3.0, uuid@^8.3.2: version "8.3.2" resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== v8-compile-cache@^2.0.3, v8-compile-cache@^2.1.1: version "2.3.0" resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA== validate-npm-package-license@^3.0.1, validate-npm-package-license@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew== dependencies: spdx-correct "^3.0.0" spdx-expression-parse "^3.0.0" validate-npm-package-name@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz#5fa912d81eb7d0c74afc140de7317f0ca7df437e" integrity sha1-X6kS2B630MdK/BQN5zF/DKffQ34= dependencies: builtins "^1.0.3" validator@13.6.0: version "13.6.0" resolved "https://registry.yarnpkg.com/validator/-/validator-13.6.0.tgz#1e71899c14cdc7b2068463cb24c1cc16f6ec7059" integrity sha512-gVgKbdbHgtxpRyR8K0O6oFZPhhB5tT1jeEHZR0Znr9Svg03U0+r9DXWMrnRAB+HtCStDQKlaIZm42tVsVjqtjg== validator@^8.0.0: version "8.2.0" resolved "https://registry.yarnpkg.com/validator/-/validator-8.2.0.tgz#3c1237290e37092355344fef78c231249dab77b9" integrity sha512-Yw5wW34fSv5spzTXNkokD6S6/Oq92d8q/t14TqsS3fAiA1RYnxSFSIZ+CY3n6PGGRCq5HhJTSepQvFUS2QUDxA== vary@^1, vary@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= vendors@^1.0.0: version "1.0.4" resolved "https://registry.yarnpkg.com/vendors/-/vendors-1.0.4.tgz#e2b800a53e7a29b93506c3cf41100d16c4c4ad8e" integrity sha512-/juG65kTL4Cy2su4P8HjtkTxk6VmJDiOPBufWniqQ6wknac6jNiXS9vU+hO3wgusiyqWlzTbVHi0dyJqRONg3w== verdaccio-audit@10.0.0: version "10.0.0" resolved "https://registry.yarnpkg.com/verdaccio-audit/-/verdaccio-audit-10.0.0.tgz#d3304d923c7f2c28c173a02425208c941f25217b" integrity sha512-Epsh+C7ZEdq39PR9QeDBTWktbeqc0zOQjMzWte6Ut5Jh6fPLZzxGF8VK8O67B6mnTwLvGy50A1aPVM97Ysh5Rw== dependencies: express "4.17.1" request "2.88.2" verdaccio-htpasswd@10.0.0: version "10.0.0" resolved "https://registry.yarnpkg.com/verdaccio-htpasswd/-/verdaccio-htpasswd-10.0.0.tgz#7a7f44e8ed4db40c53deef0f5101f2a16dce4ff1" integrity sha512-3TKwiLwl8/fbaTDawHvjSYcsyMmdARg58keP/1plv74x+Jw0sC66HbbRwQ/tPO5mqoG0UwoWW+lkO8h/OiWi9w== dependencies: "@verdaccio/file-locking" "^10.0.0" apache-md5 "1.1.2" bcryptjs "2.4.3" http-errors "1.8.0" unix-crypt-td-js "1.1.4" verdaccio@^5.1.1: version "5.1.2" resolved "https://registry.yarnpkg.com/verdaccio/-/verdaccio-5.1.2.tgz#d8bc0792302cd08af16184828ad71f51e2a8b7bd" integrity sha512-Cyi1frVJEzl75q34CrYHOo0bT7KwUSoV/b17J8NnJo47CbF5SUF/Y+qX46ZFhf4eQzipVYBkgMfI6khjvjpWSg== dependencies: "@verdaccio/commons-api" "10.0.0" "@verdaccio/local-storage" "10.0.6" "@verdaccio/readme" "10.0.0" "@verdaccio/streams" "10.0.0" "@verdaccio/ui-theme" "3.1.0" JSONStream "1.3.5" async "3.2.0" body-parser "1.19.0" clipanion "3.0.0" compression "1.7.4" cookies "0.8.0" cors "2.8.5" dayjs "1.10.6" debug "^4.3.2" envinfo "7.8.1" eslint-import-resolver-node "0.3.4" express "4.17.1" fast-safe-stringify "^2.0.8" handlebars "4.7.7" http-errors "1.8.0" js-yaml "4.1.0" jsonwebtoken "8.5.1" kleur "4.1.4" lodash "4.17.21" lru-cache "6.0.0" lunr-mutable-indexes "2.3.2" marked "2.1.3" memoizee "0.4.15" mime "2.5.2" minimatch "3.0.4" mkdirp "1.0.4" mv "2.1.1" pino "6.12.0" pkginfo "0.4.1" prettier-bytes "^1.0.4" pretty-ms "^7.0.1" request "2.88.0" semver "7.3.5" validator "13.6.0" verdaccio-audit "10.0.0" verdaccio-htpasswd "10.0.0" verror@1.10.0: version "1.10.0" resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" integrity sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA= dependencies: assert-plus "^1.0.0" core-util-is "1.0.2" extsprintf "^1.2.0" vm-browserify@^1.0.1: version "1.1.2" resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0" integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ== void-elements@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-2.0.1.tgz#c066afb582bb1cb4128d60ea92392e94d5e9dbec" integrity sha1-wGavtYK7HLQSjWDqkjkulNXp2+w= vscode-languageserver-types@^3.16.0: version "3.16.0" resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.16.0.tgz#ecf393fc121ec6974b2da3efb3155644c514e247" integrity sha512-k8luDIWJWyenLc5ToFQQMaSrqCHiLwyKPHKPQZ5zz21vM+vIVUSvsRpcbiECH4WR88K2XZqc4ScRcZ7nk/jbeA== w3c-hr-time@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd" integrity sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ== dependencies: browser-process-hrtime "^1.0.0" w3c-xmlserializer@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-1.1.2.tgz#30485ca7d70a6fd052420a3d12fd90e6339ce794" integrity sha512-p10l/ayESzrBMYWRID6xbuCKh2Fp77+sA0doRuGn4tTIMrrZVeqfpKjXHY+oDh3K4nLdPgNwMTVP6Vp4pvqbNg== dependencies: domexception "^1.0.1" webidl-conversions "^4.0.2" xml-name-validator "^3.0.0" watchpack-chokidar2@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/watchpack-chokidar2/-/watchpack-chokidar2-2.0.1.tgz#38500072ee6ece66f3769936950ea1771be1c957" integrity sha512-nCFfBIPKr5Sh61s4LPpy1Wtfi0HE8isJ3d2Yb5/Ppw2P2B/3eVSEBjKfN0fmHJSK14+31KwMKmcrzs2GM4P0Ww== dependencies: chokidar "^2.1.8" watchpack@^1.7.4: version "1.7.5" resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.7.5.tgz#1267e6c55e0b9b5be44c2023aed5437a2c26c453" integrity sha512-9P3MWk6SrKjHsGkLT2KHXdQ/9SNkyoJbabxnKOoJepsvJjJG8uYTR3yTPxPQvNDI3w4Nz1xnE0TLHK4RIVe/MQ== dependencies: graceful-fs "^4.1.2" neo-async "^2.5.0" optionalDependencies: chokidar "^3.4.1" watchpack-chokidar2 "^2.0.1" wcwidth@^1.0.0, wcwidth@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8" integrity sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g= dependencies: defaults "^1.0.3" webidl-conversions@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad" integrity sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg== webidl-conversions@^6.1.0: version "6.1.0" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-6.1.0.tgz#9111b4d7ea80acd40f5270d666621afa78b69514" integrity sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w== webpack-cli@^3.3.10: version "3.3.12" resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-3.3.12.tgz#94e9ada081453cd0aa609c99e500012fd3ad2d4a" integrity sha512-NVWBaz9k839ZH/sinurM+HcDvJOTXwSjYp1ku+5XKeOC03z8v5QitnK/x+lAxGXFyhdayoIf/GOpv85z3/xPag== dependencies: chalk "^2.4.2" cross-spawn "^6.0.5" enhanced-resolve "^4.1.1" findup-sync "^3.0.0" global-modules "^2.0.0" import-local "^2.0.0" interpret "^1.4.0" loader-utils "^1.4.0" supports-color "^6.1.0" v8-compile-cache "^2.1.1" yargs "^13.3.2" webpack-sources@^1.4.0, webpack-sources@^1.4.1: version "1.4.3" resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.4.3.tgz#eedd8ec0b928fbf1cbfe994e22d2d890f330a933" integrity sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ== dependencies: source-list-map "^2.0.0" source-map "~0.6.1" webpack@^4.41.3: version "4.46.0" resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.46.0.tgz#bf9b4404ea20a073605e0a011d188d77cb6ad542" integrity sha512-6jJuJjg8znb/xRItk7bkT0+Q7AHCYjjFnvKIWQPkNIOyRqoCGvkOs0ipeQzrqz4l5FtN5ZI/ukEHroeX/o1/5Q== dependencies: "@webassemblyjs/ast" "1.9.0" "@webassemblyjs/helper-module-context" "1.9.0" "@webassemblyjs/wasm-edit" "1.9.0" "@webassemblyjs/wasm-parser" "1.9.0" acorn "^6.4.1" ajv "^6.10.2" ajv-keywords "^3.4.1" chrome-trace-event "^1.0.2" enhanced-resolve "^4.5.0" eslint-scope "^4.0.3" json-parse-better-errors "^1.0.2" loader-runner "^2.4.0" loader-utils "^1.2.3" memory-fs "^0.4.1" micromatch "^3.1.10" mkdirp "^0.5.3" neo-async "^2.6.1" node-libs-browser "^2.2.1" schema-utils "^1.0.0" tapable "^1.1.3" terser-webpack-plugin "^1.4.3" watchpack "^1.7.4" webpack-sources "^1.4.1" websocket@^1.0.28: version "1.0.34" resolved "https://registry.yarnpkg.com/websocket/-/websocket-1.0.34.tgz#2bdc2602c08bf2c82253b730655c0ef7dcab3111" integrity sha512-PRDso2sGwF6kM75QykIesBijKSVceR6jL2G8NGYyq2XrItNC2P5/qL5XeR056GhA+Ly7JMFvJb9I312mJfmqnQ== dependencies: bufferutil "^4.0.1" debug "^2.2.0" es5-ext "^0.10.50" typedarray-to-buffer "^3.1.5" utf-8-validate "^5.0.2" yaeti "^0.0.6" whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz#5abacf777c32166a51d085d6b4f3e7d27113ddb0" integrity sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw== dependencies: iconv-lite "0.4.24" whatwg-mimetype@^2.2.0, whatwg-mimetype@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf" integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g== whatwg-url@^7.0.0: version "7.1.0" resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-7.1.0.tgz#c2c492f1eca612988efd3d2266be1b9fc6170d06" integrity sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg== dependencies: lodash.sortby "^4.7.0" tr46 "^1.0.1" webidl-conversions "^4.0.2" whatwg-url@^8.4.0: version "8.7.0" resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-8.7.0.tgz#656a78e510ff8f3937bc0bcbe9f5c0ac35941b77" integrity sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg== dependencies: lodash "^4.7.0" tr46 "^2.1.0" webidl-conversions "^6.1.0" which-boxed-primitive@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg== dependencies: is-bigint "^1.0.1" is-boolean-object "^1.1.0" is-number-object "^1.0.4" is-string "^1.0.5" is-symbol "^1.0.3" which-module@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= which-pm-runs@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/which-pm-runs/-/which-pm-runs-1.0.0.tgz#670b3afbc552e0b55df6b7780ca74615f23ad1cb" integrity sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs= which@2.0.2, which@^2.0.1, which@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== dependencies: isexe "^2.0.0" which@^1.2.1, which@^1.2.14, which@^1.2.9, which@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== dependencies: isexe "^2.0.0" wide-align@1.1.3, wide-align@^1.1.0: version "1.1.3" resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA== dependencies: string-width "^1.0.2 || 2" widest-line@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-3.1.0.tgz#8292333bbf66cb45ff0de1603b136b7ae1496eca" integrity sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg== dependencies: string-width "^4.0.0" windows-release@^3.1.0: version "3.3.3" resolved "https://registry.yarnpkg.com/windows-release/-/windows-release-3.3.3.tgz#1c10027c7225743eec6b89df160d64c2e0293999" integrity sha512-OSOGH1QYiW5yVor9TtmXKQvt2vjQqbYS+DqmsZw+r7xDwLXEeT3JGW0ZppFmHx4diyXmxt238KFR3N9jzevBRg== dependencies: execa "^1.0.0" word-wrap@^1.2.3, word-wrap@~1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== wordwrap@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" integrity sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus= worker-farm@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/worker-farm/-/worker-farm-1.7.0.tgz#26a94c5391bbca926152002f69b84a4bf772e5a8" integrity sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw== dependencies: errno "~0.1.7" workerpool@6.1.5: version "6.1.5" resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.1.5.tgz#0f7cf076b6215fd7e1da903ff6f22ddd1886b581" integrity sha512-XdKkCK0Zqc6w3iTxLckiuJ81tiD/o5rBE/m+nXpRCB+/Sq4DqkfXZ/x0jW02DG1tGsfUGXbTJyZDP+eu67haSw== wrap-ansi@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz#1fd1f67235d5b6d0fee781056001bfb694c03b09" integrity sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q== dependencies: ansi-styles "^3.2.0" string-width "^3.0.0" strip-ansi "^5.0.0" wrap-ansi@^6.2.0: version "6.2.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== dependencies: ansi-styles "^4.0.0" string-width "^4.1.0" strip-ansi "^6.0.0" wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== dependencies: ansi-styles "^4.0.0" string-width "^4.1.0" strip-ansi "^6.0.0" wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= write-file-atomic@^2.4.2: version "2.4.3" resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-2.4.3.tgz#1fd2e9ae1df3e75b8d8c367443c692d4ca81f481" integrity sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ== dependencies: graceful-fs "^4.1.11" imurmurhash "^0.1.4" signal-exit "^3.0.2" write-file-atomic@^3.0.0, write-file-atomic@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8" integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q== dependencies: imurmurhash "^0.1.4" is-typedarray "^1.0.0" signal-exit "^3.0.2" typedarray-to-buffer "^3.1.5" write-json-file@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/write-json-file/-/write-json-file-3.2.0.tgz#65bbdc9ecd8a1458e15952770ccbadfcff5fe62a" integrity sha512-3xZqT7Byc2uORAatYiP3DHUUAVEkNOswEWNs9H5KXiicRTvzYzYqKjYc4G7p+8pltvAw641lVByKVtMpf+4sYQ== dependencies: detect-indent "^5.0.0" graceful-fs "^4.1.15" make-dir "^2.1.0" pify "^4.0.1" sort-keys "^2.0.0" write-file-atomic "^2.4.2" write-json-file@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/write-json-file/-/write-json-file-4.3.0.tgz#908493d6fd23225344af324016e4ca8f702dd12d" integrity sha512-PxiShnxf0IlnQuMYOPPhPkhExoCQuTUNPOa/2JWCYTmBquU9njyyDuwRKN26IZBlp4yn1nt+Agh2HOOBl+55HQ== dependencies: detect-indent "^6.0.0" graceful-fs "^4.1.15" is-plain-obj "^2.0.0" make-dir "^3.0.0" sort-keys "^4.0.0" write-file-atomic "^3.0.0" write-pkg@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/write-pkg/-/write-pkg-4.0.0.tgz#675cc04ef6c11faacbbc7771b24c0abbf2a20039" integrity sha512-v2UQ+50TNf2rNHJ8NyWttfm/EJUBWMJcx6ZTYZr6Qp52uuegWw/lBkCtCbnYZEmPRNL61m+u67dAmGxo+HTULA== dependencies: sort-keys "^2.0.0" type-fest "^0.4.1" write-json-file "^3.2.0" write@1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/write/-/write-1.0.3.tgz#0800e14523b923a387e415123c865616aae0f5c3" integrity sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig== dependencies: mkdirp "^0.5.1" ws@^6.1.0: version "6.2.2" resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.2.tgz#dd5cdbd57a9979916097652d78f1cc5faea0c32e" integrity sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw== dependencies: async-limiter "~1.0.0" ws@^7.0.0: version "7.5.3" resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.3.tgz#160835b63c7d97bfab418fc1b8a9fced2ac01a74" integrity sha512-kQ/dHIzuLrS6Je9+uv81ueZomEwH0qVYstcAQ4/Z93K8zeko9gtAbttJWzoC5ukqXY1PpoouV3+VSOqEAFt5wg== ws@~7.4.2: version "7.4.6" resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.6.tgz#5654ca8ecdeee47c33a9a4bf6d28e2be2980377c" integrity sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A== xdg-basedir@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13" integrity sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q== xml-js@^1.6.11: version "1.6.11" resolved "https://registry.yarnpkg.com/xml-js/-/xml-js-1.6.11.tgz#927d2f6947f7f1c19a316dd8eea3614e8b18f8e9" integrity sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g== dependencies: sax "^1.2.4" xml-name-validator@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a" integrity sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw== xml2js@0.4.23, xml2js@^0.4.17: version "0.4.23" resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.23.tgz#a0c69516752421eb2ac758ee4d4ccf58843eac66" integrity sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug== dependencies: sax ">=0.6.0" xmlbuilder "~11.0.0" xmlbuilder@~11.0.0: version "11.0.1" resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-11.0.1.tgz#be9bae1c8a046e76b31127726347d0ad7002beb3" integrity sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA== xmlchars@^2.1.1: version "2.2.0" resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== y18n@^4.0.0: version "4.0.3" resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf" integrity sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ== y18n@^5.0.5: version "5.0.8" resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== yaeti@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/yaeti/-/yaeti-0.0.6.tgz#f26f484d72684cf42bedfb76970aa1608fbf9577" integrity sha1-8m9ITXJoTPQr7ft2lwqhYI+/lXc= yallist@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" integrity sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI= yallist@^3.0.0, yallist@^3.0.2, yallist@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== yallist@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== yaml-js@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/yaml-js/-/yaml-js-0.3.0.tgz#ad0893d9de881a93fd6bf936e8d89cdde309e848" integrity sha512-JbTUdsPiCkOyz+JOSqAVc19omTnUBnBQglhuclYov5HpWbEOz8y+ftqWjiMa9Pe/eF/dmCUeNgVs/VWg53GlgQ== yaml@^1.10.0, yaml@^1.10.2: version "1.10.2" resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== yargs-parser@20.2.4: version "20.2.4" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.4.tgz#b42890f14566796f85ae8e3a25290d205f154a54" integrity sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA== yargs-parser@^13.1.2: version "13.1.2" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.2.tgz#130f09702ebaeef2650d54ce6e3e5706f7a4fb38" integrity sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg== dependencies: camelcase "^5.0.0" decamelize "^1.2.0" yargs-parser@^18.1.3: version "18.1.3" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0" integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ== dependencies: camelcase "^5.0.0" decamelize "^1.2.0" yargs-parser@^20.2.2, yargs-parser@^20.2.3: version "20.2.9" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== yargs-unparser@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-2.0.0.tgz#f131f9226911ae5d9ad38c432fe809366c2325eb" integrity sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA== dependencies: camelcase "^6.0.0" decamelize "^4.0.0" flat "^5.0.2" is-plain-obj "^2.1.0" yargs@16.2.0, yargs@^16.1.1, yargs@^16.2.0: version "16.2.0" resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== dependencies: cliui "^7.0.2" escalade "^3.1.1" get-caller-file "^2.0.5" require-directory "^2.1.1" string-width "^4.2.0" y18n "^5.0.5" yargs-parser "^20.2.2" yargs@^13.3.2: version "13.3.2" resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.2.tgz#ad7ffefec1aa59565ac915f82dccb38a9c31a2dd" integrity sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw== dependencies: cliui "^5.0.0" find-up "^3.0.0" get-caller-file "^2.0.1" require-directory "^2.1.1" require-main-filename "^2.0.0" set-blocking "^2.0.0" string-width "^3.0.0" which-module "^2.0.0" y18n "^4.0.0" yargs-parser "^13.1.2" yauzl@^2.10.0: version "2.10.0" resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9" integrity sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk= dependencies: buffer-crc32 "~0.2.3" fd-slicer "~1.1.0" yocto-queue@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== z-schema@~3.18.3: version "3.18.4" resolved "https://registry.yarnpkg.com/z-schema/-/z-schema-3.18.4.tgz#ea8132b279533ee60be2485a02f7e3e42541a9a2" integrity sha512-DUOKC/IhbkdLKKiV89gw9DUauTV8U/8yJl1sjf6MtDmzevLKOF2duNJ495S3MFVjqZarr+qNGCPbkg4mu4PpLw== dependencies: lodash.get "^4.0.0" lodash.isequal "^4.0.0" validator "^8.0.0" optionalDependencies: commander "^2.7.1"