diff --git a/gh-pages/_index.html b/gh-pages/_index.html index eb8d2e97f..ac2486fc2 100644 --- a/gh-pages/_index.html +++ b/gh-pages/_index.html @@ -286,7 +286,7 @@

- + @@ -297,6 +297,12 @@

A plugin that allows users to manipulate and connect blocks using a keyboard-driven cursor.

+

+ This plugin (and its demo) is temporarily unavailable while we + develop an new and improved version; you can follow along with + our work in the blockly-keyboard-experimentation + GitHub repository. +

diff --git a/plugins/keyboard-navigation/CHANGELOG.md b/plugins/keyboard-navigation/CHANGELOG.md deleted file mode 100644 index 91f7f28b8..000000000 --- a/plugins/keyboard-navigation/CHANGELOG.md +++ /dev/null @@ -1,558 +0,0 @@ -# Change Log - -All notable changes to this project will be documented in this file. -See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. - -## [0.6.11](https://github.com/google/blockly-samples/compare/@blockly/keyboard-navigation@0.6.10...@blockly/keyboard-navigation@0.6.11) (2024-11-07) - -**Note:** Version bump only for package @blockly/keyboard-navigation - - - - - -## [0.6.10](https://github.com/google/blockly-samples/compare/@blockly/keyboard-navigation@0.6.9...@blockly/keyboard-navigation@0.6.10) (2024-09-26) - - -### Bug Fixes - -* **field-*:** Remove unneeded `fieldRegistry.unregister` calls ([#2454](https://github.com/google/blockly-samples/issues/2454)) ([b3ba30e](https://github.com/google/blockly-samples/commit/b3ba30e23dddf0bd98c266659aa229ba6ba685b0)), closes [#2453](https://github.com/google/blockly-samples/issues/2453) - - - - - -## [0.6.9](https://github.com/google/blockly-samples/compare/@blockly/keyboard-navigation@0.6.8...@blockly/keyboard-navigation@0.6.9) (2024-08-29) - - -### Bug Fixes - -* predeploy plugins ([#2449](https://github.com/google/blockly-samples/issues/2449)) ([6b36d8b](https://github.com/google/blockly-samples/commit/6b36d8b344a969f79d89bbc7dcee29ae554759f9)) - - - - - -## [0.6.8](https://github.com/google/blockly-samples/compare/@blockly/keyboard-navigation@0.6.7...@blockly/keyboard-navigation@0.6.8) (2024-08-22) - - -### Bug Fixes - -* lerna v8 breaking our whole shebangle ([#2446](https://github.com/google/blockly-samples/issues/2446)) ([b8b4c21](https://github.com/google/blockly-samples/commit/b8b4c21d4eaf81f527336ae46f6679ff99ac23c3)) - - - - - -## [0.6.7](https://github.com/google/blockly-samples/compare/@blockly/keyboard-navigation@0.6.6...@blockly/keyboard-navigation@0.6.7) (2024-08-08) - - -### Bug Fixes - -* **keyboard-navigation:** remove references to blockly.fieldcolour ([#2441](https://github.com/google/blockly-samples/issues/2441)) ([81787b7](https://github.com/google/blockly-samples/commit/81787b7008b2e255da74487ee32eafeff2477177)) - - - -## [0.6.6](https://github.com/google/blockly-samples/compare/@blockly/keyboard-navigation@0.6.5...@blockly/keyboard-navigation@0.6.6) (2024-08-01) - -**Note:** Version bump only for package @blockly/keyboard-navigation - - - - - -## [0.6.5](https://github.com/google/blockly-samples/compare/@blockly/keyboard-navigation@0.6.4...@blockly/keyboard-navigation@0.6.5) (2024-07-25) - -**Note:** Version bump only for package @blockly/keyboard-navigation - - - - - -## [0.6.4](https://github.com/google/blockly-samples/compare/@blockly/keyboard-navigation@0.6.3...@blockly/keyboard-navigation@0.6.4) (2024-07-11) - - -### Reverts - -* Revert "chore(deps): Bump the npm_and_yarn group across 54 directories with 19 updates (#2403)" (#2410) ([bad8ffb](https://github.com/google/blockly-samples/commit/bad8ffbf85caa4e5b68d2f010cd0deaa9e3dd98f)), closes [#2403](https://github.com/google/blockly-samples/issues/2403) [#2410](https://github.com/google/blockly-samples/issues/2410) -* Revert "chore(deps): Bump the npm_and_yarn group across 6 directories with 11…" (#2408) ([95b13c7](https://github.com/google/blockly-samples/commit/95b13c7de3b6b3892b5c04bdc02a798ff04061b4)), closes [#2408](https://github.com/google/blockly-samples/issues/2408) - - - -## [0.6.3](https://github.com/google/blockly-samples/compare/@blockly/keyboard-navigation@0.6.2...@blockly/keyboard-navigation@0.6.3) (2024-07-04) - -**Note:** Version bump only for package @blockly/keyboard-navigation - - - - - -## [0.6.2](https://github.com/google/blockly-samples/compare/@blockly/keyboard-navigation@0.6.1...@blockly/keyboard-navigation@0.6.2) (2024-06-06) - -**Note:** Version bump only for package @blockly/keyboard-navigation - - - - - -## [0.6.1](https://github.com/google/blockly-samples/compare/@blockly/keyboard-navigation@0.6.0...@blockly/keyboard-navigation@0.6.1) (2024-05-30) - - -### Bug Fixes - -* multiple blockly instances ([#2375](https://github.com/google/blockly-samples/issues/2375)) ([b231e59](https://github.com/google/blockly-samples/commit/b231e598f2f5f5b0abbfd01d981e35572ad50a26)) - - - -## [0.6.0](https://github.com/google/blockly-samples/compare/@blockly/keyboard-navigation@0.5.13...@blockly/keyboard-navigation@0.6.0) (2024-05-21) - - -### ⚠ BREAKING CHANGES - -* update all plugins to v11 of blockly -* **dev-scripts:** This PR removes the support that was added - -### Features - -* support keyboard navigation of flyout buttons ([#2200](https://github.com/google/blockly-samples/issues/2200)) ([c2abe4d](https://github.com/google/blockly-samples/commit/c2abe4da9808f64161d5dc89a5e4d1b546ec279b)) -* update all plugins to v11 of blockly ([5fcd3e7](https://github.com/google/blockly-samples/commit/5fcd3e7d53eaadffe9bda9a378b404d34b2f8be2)) - - -### Bug Fixes - -* build for the v11 branch ([#2268](https://github.com/google/blockly-samples/issues/2268)) ([c7a4fc5](https://github.com/google/blockly-samples/commit/c7a4fc5e72c5e8d9e9bc926bbbbadd1eb31792fc)) -* Call blocks handle both manual disabling and disabled defs ([#2334](https://github.com/google/blockly-samples/issues/2334)) ([5eade55](https://github.com/google/blockly-samples/commit/5eade55779c4022d14ad4472ff32c93c78199887)) -* **dev-scripts:** Fixes, refactoring and simplification of `webpack.config.js` and `'blockly'` imports ([#2229](https://github.com/google/blockly-samples/issues/2229)) ([f5ffdb9](https://github.com/google/blockly-samples/commit/f5ffdb961e3b60ddb164087f4bddc4e6215906b7)), closes [#335](https://github.com/google/blockly-samples/issues/335) [#335](https://github.com/google/blockly-samples/issues/335) [#1630](https://github.com/google/blockly-samples/issues/1630) [#335](https://github.com/google/blockly-samples/issues/335) [#226](https://github.com/google/blockly-samples/issues/226) [google/blockly#7822](https://github.com/google/blockly/issues/7822) [google/blockly#7822](https://github.com/google/blockly/issues/7822) [/github.com/google/blockly-samples/pull/2229#issuecomment-1979123919](https://github.com/google//github.com/google/blockly-samples/pull/2229/issues/issuecomment-1979123919) -* update keyboard nav to use proper getContents ([#2342](https://github.com/google/blockly-samples/issues/2342)) ([4d429c0](https://github.com/google/blockly-samples/commit/4d429c092c59045b6a284f0d62a4afcda994e90d)) - - - -## [0.5.13](https://github.com/google/blockly-samples/compare/@blockly/keyboard-navigation@0.5.12...@blockly/keyboard-navigation@0.5.13) (2024-05-16) - -**Note:** Version bump only for package @blockly/keyboard-navigation - - - - - -## [0.5.12](https://github.com/google/blockly-samples/compare/@blockly/keyboard-navigation@0.5.11...@blockly/keyboard-navigation@0.5.12) (2024-05-09) - -**Note:** Version bump only for package @blockly/keyboard-navigation - - - - - -## [0.5.11](https://github.com/google/blockly-samples/compare/@blockly/keyboard-navigation@0.5.10...@blockly/keyboard-navigation@0.5.11) (2024-04-25) - -**Note:** Version bump only for package @blockly/keyboard-navigation - - - - - -## [0.5.10](https://github.com/google/blockly-samples/compare/@blockly/keyboard-navigation@0.5.9...@blockly/keyboard-navigation@0.5.10) (2024-04-04) - -**Note:** Version bump only for package @blockly/keyboard-navigation - - - - - -## [0.5.9](https://github.com/google/blockly-samples/compare/@blockly/keyboard-navigation@0.5.8...@blockly/keyboard-navigation@0.5.9) (2024-03-28) - -**Note:** Version bump only for package @blockly/keyboard-navigation - - - - - -## [0.5.8](https://github.com/google/blockly-samples/compare/@blockly/keyboard-navigation@0.5.7...@blockly/keyboard-navigation@0.5.8) (2024-02-08) - -**Note:** Version bump only for package @blockly/keyboard-navigation - - - - - -## [0.5.7](https://github.com/google/blockly-samples/compare/@blockly/keyboard-navigation@0.5.6...@blockly/keyboard-navigation@0.5.7) (2023-12-14) - -**Note:** Version bump only for package @blockly/keyboard-navigation - - - - - -## [0.5.6](https://github.com/google/blockly-samples/compare/@blockly/keyboard-navigation@0.5.5...@blockly/keyboard-navigation@0.5.6) (2023-12-07) - -**Note:** Version bump only for package @blockly/keyboard-navigation - - - - - -## [0.5.5](https://github.com/google/blockly-samples/compare/@blockly/keyboard-navigation@0.5.4...@blockly/keyboard-navigation@0.5.5) (2023-12-07) - -**Note:** Version bump only for package @blockly/keyboard-navigation - - - - - -## [0.5.4](https://github.com/google/blockly-samples/compare/@blockly/keyboard-navigation@0.5.3...@blockly/keyboard-navigation@0.5.4) (2023-11-16) - - -### Bug Fixes - -* resolved 15 ESLint warnings ([#2065](https://github.com/google/blockly-samples/issues/2065)) ([2436337](https://github.com/google/blockly-samples/commit/243633746542bb1518fe2893c0421a5a6f79fd32)) - - - -## [0.5.3](https://github.com/google/blockly-samples/compare/@blockly/keyboard-navigation@0.5.2...@blockly/keyboard-navigation@0.5.3) (2023-11-09) - -**Note:** Version bump only for package @blockly/keyboard-navigation - - - - - -## [0.5.2](https://github.com/google/blockly-samples/compare/@blockly/keyboard-navigation@0.5.1...@blockly/keyboard-navigation@0.5.2) (2023-11-02) - -**Note:** Version bump only for package @blockly/keyboard-navigation - - - - - -## [0.5.1](https://github.com/google/blockly-samples/compare/@blockly/keyboard-navigation@0.5.0...@blockly/keyboard-navigation@0.5.1) (2023-10-30) - -**Note:** Version bump only for package @blockly/keyboard-navigation - - - - - -## [0.5.0](https://github.com/google/blockly-samples/compare/@blockly/keyboard-navigation@0.4.7...@blockly/keyboard-navigation@0.5.0) (2023-09-28) - - -### ⚠ BREAKING CHANGES - -* changes for v10.2.0 (#1989) - -### release - -* changes for v10.2.0 ([#1989](https://github.com/google/blockly-samples/issues/1989)) ([4d81ea7](https://github.com/google/blockly-samples/commit/4d81ea7254412ef199a270fc740c2f45755a2725)), closes [#1844](https://github.com/google/blockly-samples/issues/1844) [#1845](https://github.com/google/blockly-samples/issues/1845) [#1863](https://github.com/google/blockly-samples/issues/1863) [#1880](https://github.com/google/blockly-samples/issues/1880) [#1885](https://github.com/google/blockly-samples/issues/1885) [#1886](https://github.com/google/blockly-samples/issues/1886) [#1887](https://github.com/google/blockly-samples/issues/1887) [#1888](https://github.com/google/blockly-samples/issues/1888) [#1919](https://github.com/google/blockly-samples/issues/1919) [#1920](https://github.com/google/blockly-samples/issues/1920) [#1983](https://github.com/google/blockly-samples/issues/1983) [#1984](https://github.com/google/blockly-samples/issues/1984) [#1985](https://github.com/google/blockly-samples/issues/1985) [#1991](https://github.com/google/blockly-samples/issues/1991) - - - -## [0.4.7](https://github.com/google/blockly-samples/compare/@blockly/keyboard-navigation@0.4.6...@blockly/keyboard-navigation@0.4.7) (2023-09-28) - - -### Bug Fixes - -* make keyboard nav compatible with v10.2.0 ([#1990](https://github.com/google/blockly-samples/issues/1990)) ([e0915b3](https://github.com/google/blockly-samples/commit/e0915b36afb7d049665673e89c4340bc01cdf08c)) - - - -## [0.4.6](https://github.com/google/blockly-samples/compare/@blockly/keyboard-navigation@0.4.5...@blockly/keyboard-navigation@0.4.6) (2023-09-14) - -**Note:** Version bump only for package @blockly/keyboard-navigation - - - - - -## [0.4.5](https://github.com/google/blockly-samples/compare/@blockly/keyboard-navigation@0.4.4...@blockly/keyboard-navigation@0.4.5) (2023-08-17) - -**Note:** Version bump only for package @blockly/keyboard-navigation - - - - - -## [0.4.4](https://github.com/google/blockly-samples/compare/@blockly/keyboard-navigation@0.4.3...@blockly/keyboard-navigation@0.4.4) (2023-07-24) - -**Note:** Version bump only for package @blockly/keyboard-navigation - - - - - -## [0.4.3](https://github.com/google/blockly-samples/compare/@blockly/keyboard-navigation@0.4.2...@blockly/keyboard-navigation@0.4.3) (2023-07-20) - -**Note:** Version bump only for package @blockly/keyboard-navigation - - - - - -## [0.4.2](https://github.com/google/blockly-samples/compare/@blockly/keyboard-navigation@0.4.1...@blockly/keyboard-navigation@0.4.2) (2023-07-13) - -**Note:** Version bump only for package @blockly/keyboard-navigation - - - - - -## [0.4.1](https://github.com/google/blockly-samples/compare/@blockly/keyboard-navigation@0.4.0...@blockly/keyboard-navigation@0.4.1) (2023-07-06) - -**Note:** Version bump only for package @blockly/keyboard-navigation - - - - - -## [0.4.0](https://github.com/google/blockly-samples/compare/@blockly/keyboard-navigation@0.3.18...@blockly/keyboard-navigation@0.4.0) (2023-06-28) - - -### ⚠ BREAKING CHANGES - -* update all plugins to use blockly-v10.0.0 (#1744) - -### Features - -* update all plugins to use blockly-v10.0.0 ([#1744](https://github.com/google/blockly-samples/issues/1744)) ([6cc88cb](https://github.com/google/blockly-samples/commit/6cc88cbef39d4ad664a668d3d46eb29ba7292f9c)) - - - -## [0.3.18](https://github.com/google/blockly-samples/compare/@blockly/keyboard-navigation@0.3.17...@blockly/keyboard-navigation@0.3.18) (2023-06-22) - -**Note:** Version bump only for package @blockly/keyboard-navigation - - - - - -## [0.3.17](https://github.com/google/blockly-samples/compare/@blockly/keyboard-navigation@0.3.16...@blockly/keyboard-navigation@0.3.17) (2023-05-18) - -**Note:** Version bump only for package @blockly/keyboard-navigation - - - - - -## [0.3.16](https://github.com/google/blockly-samples/compare/@blockly/keyboard-navigation@0.3.15...@blockly/keyboard-navigation@0.3.16) (2023-05-11) - -**Note:** Version bump only for package @blockly/keyboard-navigation - - - - - -## [0.3.15](https://github.com/google/blockly-samples/compare/@blockly/keyboard-navigation@0.3.14...@blockly/keyboard-navigation@0.3.15) (2023-05-04) - -**Note:** Version bump only for package @blockly/keyboard-navigation - - - - - -## [0.3.14](https://github.com/google/blockly-samples/compare/@blockly/keyboard-navigation@0.3.13...@blockly/keyboard-navigation@0.3.14) (2023-04-27) - -**Note:** Version bump only for package @blockly/keyboard-navigation - - - - - -## [0.3.13](https://github.com/google/blockly-samples/compare/@blockly/keyboard-navigation@0.3.12...@blockly/keyboard-navigation@0.3.13) (2023-04-13) - -**Note:** Version bump only for package @blockly/keyboard-navigation - - - - - -## [0.3.12](https://github.com/google/blockly-samples/compare/@blockly/keyboard-navigation@0.3.11...@blockly/keyboard-navigation@0.3.12) (2023-03-23) - -**Note:** Version bump only for package @blockly/keyboard-navigation - - - - - -## [0.3.11](https://github.com/google/blockly-samples/compare/@blockly/keyboard-navigation@0.3.10...@blockly/keyboard-navigation@0.3.11) (2023-03-09) - -**Note:** Version bump only for package @blockly/keyboard-navigation - - - - - -## [0.3.10](https://github.com/google/blockly-samples/compare/@blockly/keyboard-navigation@0.3.9...@blockly/keyboard-navigation@0.3.10) (2023-03-02) - -**Note:** Version bump only for package @blockly/keyboard-navigation - - - - - -## [0.3.9](https://github.com/google/blockly-samples/compare/@blockly/keyboard-navigation@0.3.8...@blockly/keyboard-navigation@0.3.9) (2023-02-23) - - -### Bug Fixes - -* remove prepublishOnly scripts ([#1579](https://github.com/google/blockly-samples/issues/1579)) ([27da6cd](https://github.com/google/blockly-samples/commit/27da6cd04c38f6ba417f4e7446bb6218c475448d)) - - - -## [0.3.8](https://github.com/google/blockly-samples/compare/@blockly/keyboard-navigation@0.3.7...@blockly/keyboard-navigation@0.3.8) (2023-02-23) - -**Note:** Version bump only for package @blockly/keyboard-navigation - - - - - -## [0.3.7](https://github.com/google/blockly-samples/compare/@blockly/keyboard-navigation@0.3.6...@blockly/keyboard-navigation@0.3.7) (2023-02-16) - -**Note:** Version bump only for package @blockly/keyboard-navigation - - - - - -## [0.3.6](https://github.com/google/blockly-samples/compare/@blockly/keyboard-navigation@0.3.5...@blockly/keyboard-navigation@0.3.6) (2023-02-09) - -**Note:** Version bump only for package @blockly/keyboard-navigation - - - - - -## [0.3.5](https://github.com/google/blockly-samples/compare/@blockly/keyboard-navigation@0.3.4...@blockly/keyboard-navigation@0.3.5) (2023-01-26) - -**Note:** Version bump only for package @blockly/keyboard-navigation - - - - - -## [0.3.4](https://github.com/google/blockly-samples/compare/@blockly/keyboard-navigation@0.3.3...@blockly/keyboard-navigation@0.3.4) (2023-01-05) - -**Note:** Version bump only for package @blockly/keyboard-navigation - - - - - -## [0.3.3](https://github.com/google/blockly-samples/compare/@blockly/keyboard-navigation@0.3.2...@blockly/keyboard-navigation@0.3.3) (2022-12-15) - -**Note:** Version bump only for package @blockly/keyboard-navigation - - - - - -## [0.3.2](https://github.com/google/blockly-samples/compare/@blockly/keyboard-navigation@0.3.1...@blockly/keyboard-navigation@0.3.2) (2022-10-27) - -**Note:** Version bump only for package @blockly/keyboard-navigation - - - - - -## [0.3.1](https://github.com/google/blockly-samples/compare/@blockly/keyboard-navigation@0.3.0...@blockly/keyboard-navigation@0.3.1) (2022-10-13) - -**Note:** Version bump only for package @blockly/keyboard-navigation - - - - - -## [0.3.0](https://github.com/google/blockly-samples/compare/@blockly/keyboard-navigation@0.2.22...@blockly/keyboard-navigation@0.3.0) (2022-10-05) - - -### ⚠ BREAKING CHANGES - -* update peer and devDependencies of all plugins to require Blockly v9 (#1314) - -### Bug Fixes - -* keyboard navigation tests ([#1311](https://github.com/google/blockly-samples/issues/1311)) ([8299646](https://github.com/google/blockly-samples/commit/8299646ecf26bf0a95df11bfb24ae2e219c88176)) -* references to deprecated functions in v9 ([#1313](https://github.com/google/blockly-samples/issues/1313)) ([cb2e679](https://github.com/google/blockly-samples/commit/cb2e67987e0b62a77c26adc660cc6ade1ba67954)) - - -### Miscellaneous Chores - -* update peer and devDependencies of all plugins to require Blockly v9 ([#1314](https://github.com/google/blockly-samples/issues/1314)) ([03d4912](https://github.com/google/blockly-samples/commit/03d4912c42c8de0f30493037ccc28dddaea0f266)) - - - -## 0.2.22 (2022-08-11) - - -### Bug Fixes - -* Remove the deprecated block-extension-tooltip ([#1215](https://github.com/google/blockly-samples/issues/1215)) ([a044478](https://github.com/google/blockly-samples/commit/a044478c86a73e3065bc866e427f175cbec6fc13)) - - - - - -## 0.2.21 (2022-08-04) - - -### Bug Fixes - -* fix the name of the package of suggested-blocks ([#1214](https://github.com/google/blockly-samples/issues/1214)) ([35aa8ec](https://github.com/google/blockly-samples/commit/35aa8ec73a60a4eb5b1e80cb2fc71dcd83d05e27)) - - - - - -## 0.2.19 (2022-07-21) - -**Note:** Version bump only for package @blockly/keyboard-navigation - - - - - -## 0.2.18 (2022-07-06) - - -### Bug Fixes - -* Update blockly-react to use functional components instead of class components. ([#1178](https://github.com/google/blockly-samples/issues/1178)) ([fa21187](https://github.com/google/blockly-samples/commit/fa21187cdbe4ec3a5c69f185540dd68a98eb69d7)) - - - - - -## 0.2.17 (2022-06-27) - - -### Bug Fixes - -* Update package import for keyboard navigation demo ([#1170](https://github.com/google/blockly-samples/issues/1170)) ([69c1725](https://github.com/google/blockly-samples/commit/69c1725b775279fcc397dc178935208d5f42b08c)) - - - - - -## 0.2.16 (2022-06-21) - -**Note:** Version bump only for package @blockly/keyboard-navigation - - - - - -## 0.2.15 (2022-06-08) - - -### Bug Fixes - -* package versions to support patch releases ([#1150](https://github.com/google/blockly-samples/issues/1150)) ([e1ae378](https://github.com/google/blockly-samples/commit/e1ae378d779531621c3d948566257d069002963f)) - - - - - -## 0.2.14 (2022-06-02) - - -### Bug Fixes - -* add git identity ([#1156](https://github.com/google/blockly-samples/issues/1156)) ([8d80924](https://github.com/google/blockly-samples/commit/8d809243b277375beb2ce75d4e157b5e17f78193)) diff --git a/plugins/keyboard-navigation/GETSTARTED.md b/plugins/keyboard-navigation/GETSTARTED.md deleted file mode 100644 index 99e93c7e9..000000000 --- a/plugins/keyboard-navigation/GETSTARTED.md +++ /dev/null @@ -1,24 +0,0 @@ -This package was bootstrapped with [@blockly/create-package](https://www.npmjs.com/package/@blockly/create-package). - -## Available Scripts - -In this directory, you can run: - -### `npm start` - -Runs the package in development mode. - -Open [http://localhost:3000/test](http://localhost:3000/test) to view the test -playground in the browser. The page will reload if you make edits. - -### `npm run build` - -Builds the package into the `dist` directory. - -### `npm run lint` - -Runs eslint on the `src` and `test` directories. - -### `npm run clean` - -Deletes the `dist` and `build` directories if they exist. diff --git a/plugins/keyboard-navigation/README.md b/plugins/keyboard-navigation/README.md deleted file mode 100644 index abf069175..000000000 --- a/plugins/keyboard-navigation/README.md +++ /dev/null @@ -1,52 +0,0 @@ -# @blockly/keyboard-navigation [![Built on Blockly](https://tinyurl.com/built-on-blockly)](https://github.com/google/blockly) - -A [Blockly](https://www.npmjs.com/package/blockly) plugin that adds keyboard -navigation to Blockly. This allows users to use the keyboard to navigate the -toolbox and the blocks. More information on keyboard navigation can be found -on our [keyboard navigation documentation page](https://developers.google.com/blockly/guides/configure/web/keyboard-nav). - -## Installation - -### Yarn - -``` -yarn add @blockly/keyboard-navigation -``` - -### npm - -``` -npm install @blockly/keyboard-navigation --save -``` - -## Usage - -```js -import * as Blockly from 'blockly'; -import {NavigationController} from '@blockly/keyboard-navigation'; -// Inject Blockly. -const workspace = Blockly.inject('blocklyDiv', { - toolbox: toolboxCategories, -}); -// Initialize plugin. -const navigationController = new NavigationController(); -navigationController.init(); -navigationController.addWorkspace(workspace); -// Turns on keyboard navigation. -navigationController.enable(workspace); -``` - -## API - -This plugin exports the following classes: - -- `NavigationController`: Class in charge of registering all keyboard shortcuts. -- `Navigation`: This holds all the functions necessary to navigate around Blockly using the keyboard. -- `FlyoutCursor`: Cursor in charge of navigating the flyout. -- `LineCursor`: Alternative cursor that tries to navigate blocks like lines of code. - -You should only need to use these if you plan on changing the default functionality. - -## License - -Apache 2.0 diff --git a/plugins/keyboard-navigation/package-lock.json b/plugins/keyboard-navigation/package-lock.json deleted file mode 100644 index a8cd4831c..000000000 --- a/plugins/keyboard-navigation/package-lock.json +++ /dev/null @@ -1,3070 +0,0 @@ -{ - "name": "@blockly/keyboard-navigation", - "version": "0.6.11", - "lockfileVersion": 2, - "requires": true, - "packages": { - "": { - "name": "@blockly/keyboard-navigation", - "version": "0.6.11", - "license": "Apache-2.0", - "devDependencies": { - "chai": "^4.2.0", - "jsdom": "^16.4.0", - "jsdom-global": "^3.0.2", - "mocha": "^10.7.0", - "sinon": "^9.0.1" - }, - "engines": { - "node": ">=8.17.0" - }, - "peerDependencies": { - "blockly": "^11.0.0" - } - }, - "node_modules/@sinonjs/commons": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", - "integrity": "sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==", - "dev": true, - "dependencies": { - "type-detect": "4.0.8" - } - }, - "node_modules/@sinonjs/fake-timers": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz", - "integrity": "sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA==", - "dev": true, - "dependencies": { - "@sinonjs/commons": "^1.7.0" - } - }, - "node_modules/@sinonjs/samsam": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-5.3.1.tgz", - "integrity": "sha512-1Hc0b1TtyfBu8ixF/tpfSHTVWKwCBLY4QJbkgnE7HcwyvT2xArDxb4K7dMgqRm3szI+LJbzmW/s4xxEhv6hwDg==", - "dev": true, - "dependencies": { - "@sinonjs/commons": "^1.6.0", - "lodash.get": "^4.4.2", - "type-detect": "^4.0.8" - } - }, - "node_modules/@sinonjs/text-encoding": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz", - "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", - "dev": true - }, - "node_modules/@tootallnate/once": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", - "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, - "node_modules/abab": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", - "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", - "dev": true - }, - "node_modules/acorn": { - "version": "8.8.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", - "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-globals": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", - "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", - "dev": true, - "dependencies": { - "acorn": "^7.1.1", - "acorn-walk": "^7.1.1" - } - }, - "node_modules/acorn-globals/node_modules/acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-walk": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", - "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/ansi-colors": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", - "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/assertion-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", - "dev": true - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browser-process-hrtime": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", - "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", - "dev": true - }, - "node_modules/browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", - "dev": true - }, - "node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/chai": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.6.tgz", - "integrity": "sha512-bbcp3YfHCUzMOvKqsztczerVgBKSsEijCySNlHHbX3VG1nskvqjz5Rfso1gGwD6w6oOV3eI60pKuMOV5MV7p3Q==", - "dev": true, - "dependencies": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.2", - "deep-eql": "^3.0.1", - "get-func-name": "^2.0.0", - "loupe": "^2.3.1", - "pathval": "^1.1.1", - "type-detect": "^4.0.5" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/check-error": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", - "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, - "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" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/cssom": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz", - "integrity": "sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==", - "dev": true - }, - "node_modules/cssstyle": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", - "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", - "dev": true, - "dependencies": { - "cssom": "~0.3.6" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cssstyle/node_modules/cssom": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", - "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", - "dev": true - }, - "node_modules/data-urls": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz", - "integrity": "sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==", - "dev": true, - "dependencies": { - "abab": "^2.0.3", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^8.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/decamelize": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", - "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/decimal.js": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", - "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==", - "dev": true - }, - "node_modules/deep-eql": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", - "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", - "dev": true, - "dependencies": { - "type-detect": "^4.0.0" - }, - "engines": { - "node": ">=0.12" - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/diff": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", - "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", - "dev": true, - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/domexception": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz", - "integrity": "sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==", - "dev": true, - "dependencies": { - "webidl-conversions": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/domexception/node_modules/webidl-conversions": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz", - "integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/escalade": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", - "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/escodegen": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz", - "integrity": "sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==", - "dev": true, - "dependencies": { - "esprima": "^4.0.1", - "estraverse": "^5.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1" - }, - "bin": { - "escodegen": "bin/escodegen.js", - "esgenerate": "bin/esgenerate.js" - }, - "engines": { - "node": ">=6.0" - }, - "optionalDependencies": { - "source-map": "~0.6.1" - } - }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", - "dev": true - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/flat": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", - "dev": true, - "bin": { - "flat": "cli.js" - } - }, - "node_modules/form-data": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", - "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", - "dev": true, - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-func-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", - "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/glob/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/glob/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true, - "bin": { - "he": "bin/he" - } - }, - "node_modules/html-encoding-sniffer": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", - "integrity": "sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==", - "dev": true, - "dependencies": { - "whatwg-encoding": "^1.0.5" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/http-proxy-agent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", - "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", - "dev": true, - "dependencies": { - "@tootallnate/once": "1", - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "dev": true, - "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "dev": true, - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-potential-custom-element-name": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", - "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", - "dev": true - }, - "node_modules/is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", - "dev": true - }, - "node_modules/jsdom": { - "version": "16.7.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.7.0.tgz", - "integrity": "sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw==", - "dev": true, - "dependencies": { - "abab": "^2.0.5", - "acorn": "^8.2.4", - "acorn-globals": "^6.0.0", - "cssom": "^0.4.4", - "cssstyle": "^2.3.0", - "data-urls": "^2.0.0", - "decimal.js": "^10.2.1", - "domexception": "^2.0.1", - "escodegen": "^2.0.0", - "form-data": "^3.0.0", - "html-encoding-sniffer": "^2.0.1", - "http-proxy-agent": "^4.0.1", - "https-proxy-agent": "^5.0.0", - "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.0", - "parse5": "6.0.1", - "saxes": "^5.0.1", - "symbol-tree": "^3.2.4", - "tough-cookie": "^4.0.0", - "w3c-hr-time": "^1.0.2", - "w3c-xmlserializer": "^2.0.0", - "webidl-conversions": "^6.1.0", - "whatwg-encoding": "^1.0.5", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^8.5.0", - "ws": "^7.4.6", - "xml-name-validator": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "canvas": "^2.5.0" - }, - "peerDependenciesMeta": { - "canvas": { - "optional": true - } - } - }, - "node_modules/jsdom-global": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/jsdom-global/-/jsdom-global-3.0.2.tgz", - "integrity": "sha1-a9KZwTsMRiay2iwDk81DhdYGrLk=", - "dev": true, - "peerDependencies": { - "jsdom": ">=10.0.0" - } - }, - "node_modules/just-extend": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.2.1.tgz", - "integrity": "sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==", - "dev": true - }, - "node_modules/levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", - "dev": true, - "dependencies": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, - "node_modules/lodash.get": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", - "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", - "dev": true - }, - "node_modules/log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dev": true, - "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/loupe": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.1.tgz", - "integrity": "sha512-EN1D3jyVmaX4tnajVlfbREU4axL647hLec1h/PXAb8CPDMJiYitcWF2UeLVNttRqaIqQs4x+mRvXf+d+TlDrCA==", - "dev": true, - "dependencies": { - "get-func-name": "^2.0.0" - } - }, - "node_modules/mime-db": { - "version": "1.51.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz", - "integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.34", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz", - "integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==", - "dev": true, - "dependencies": { - "mime-db": "1.51.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mocha": { - "version": "10.7.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.7.0.tgz", - "integrity": "sha512-v8/rBWr2VO5YkspYINnvu81inSz2y3ODJrhO175/Exzor1RcEZZkizgE2A+w/CAXXoESS8Kys5E62dOHGHzULA==", - "dev": true, - "dependencies": { - "ansi-colors": "^4.1.3", - "browser-stdout": "^1.3.1", - "chokidar": "^3.5.3", - "debug": "^4.3.5", - "diff": "^5.2.0", - "escape-string-regexp": "^4.0.0", - "find-up": "^5.0.0", - "glob": "^8.1.0", - "he": "^1.2.0", - "js-yaml": "^4.1.0", - "log-symbols": "^4.1.0", - "minimatch": "^5.1.6", - "ms": "^2.1.3", - "serialize-javascript": "^6.0.2", - "strip-json-comments": "^3.1.1", - "supports-color": "^8.1.1", - "workerpool": "^6.5.1", - "yargs": "^16.2.0", - "yargs-parser": "^20.2.9", - "yargs-unparser": "^2.0.0" - }, - "bin": { - "_mocha": "bin/_mocha", - "mocha": "bin/mocha.js" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/mocha/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "node_modules/mocha/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/mocha/node_modules/debug": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", - "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/mocha/node_modules/debug/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/mocha/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/mocha/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/mocha/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - }, - "node_modules/mocha/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/nise": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/nise/-/nise-4.1.0.tgz", - "integrity": "sha512-eQMEmGN/8arp0xsvGoQ+B1qvSkR73B1nWSCh7nOt5neMCtwcQVYQGdzQMhcNscktTsWB54xnlSQFzOAPJD8nXA==", - "dev": true, - "dependencies": { - "@sinonjs/commons": "^1.7.0", - "@sinonjs/fake-timers": "^6.0.0", - "@sinonjs/text-encoding": "^0.7.1", - "just-extend": "^4.0.2", - "path-to-regexp": "^1.7.0" - } - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nwsapi": { - "version": "2.2.7", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.7.tgz", - "integrity": "sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ==", - "dev": true - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "dev": true, - "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" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/parse5": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", - "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", - "dev": true - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-to-regexp": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", - "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", - "dev": true, - "dependencies": { - "isarray": "0.0.1" - } - }, - "node_modules/pathval": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", - "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/psl": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", - "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==", - "dev": true - }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/querystringify": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", - "dev": true - }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", - "dev": true - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true - }, - "node_modules/saxes": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", - "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", - "dev": true, - "dependencies": { - "xmlchars": "^2.2.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/serialize-javascript": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", - "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", - "dev": true, - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "node_modules/sinon": { - "version": "9.2.4", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-9.2.4.tgz", - "integrity": "sha512-zljcULZQsJxVra28qIAL6ow1Z9tpattkCTEJR4RBP3TGc00FcttsP5pK284Nas5WjMZU5Yzy3kAIp3B3KRf5Yg==", - "dev": true, - "dependencies": { - "@sinonjs/commons": "^1.8.1", - "@sinonjs/fake-timers": "^6.0.1", - "@sinonjs/samsam": "^5.3.1", - "diff": "^4.0.2", - "nise": "^4.0.4", - "supports-color": "^7.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/sinon" - } - }, - "node_modules/sinon/node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true, - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/symbol-tree": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", - "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", - "dev": true - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/tough-cookie": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", - "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", - "dev": true, - "dependencies": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.2.0", - "url-parse": "^1.5.3" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/tough-cookie/node_modules/universalify": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", - "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", - "dev": true, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/tr46": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz", - "integrity": "sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==", - "dev": true, - "dependencies": { - "punycode": "^2.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", - "dev": true, - "dependencies": { - "prelude-ls": "~1.1.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/url-parse": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", - "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", - "dev": true, - "dependencies": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" - } - }, - "node_modules/w3c-hr-time": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", - "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", - "dev": true, - "dependencies": { - "browser-process-hrtime": "^1.0.0" - } - }, - "node_modules/w3c-xmlserializer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz", - "integrity": "sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==", - "dev": true, - "dependencies": { - "xml-name-validator": "^3.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/webidl-conversions": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", - "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==", - "dev": true, - "engines": { - "node": ">=10.4" - } - }, - "node_modules/whatwg-encoding": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", - "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", - "dev": true, - "dependencies": { - "iconv-lite": "0.4.24" - } - }, - "node_modules/whatwg-mimetype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", - "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==", - "dev": true - }, - "node_modules/whatwg-url": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.7.0.tgz", - "integrity": "sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg==", - "dev": true, - "dependencies": { - "lodash": "^4.7.0", - "tr46": "^2.1.0", - "webidl-conversions": "^6.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/word-wrap": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", - "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/workerpool": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz", - "integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==", - "dev": true - }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true - }, - "node_modules/ws": { - "version": "7.5.10", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", - "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", - "dev": true, - "engines": { - "node": ">=8.3.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/xml-name-validator": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", - "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", - "dev": true - }, - "node_modules/xmlchars": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", - "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", - "dev": true - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, - "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" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs-unparser": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", - "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", - "dev": true, - "dependencies": { - "camelcase": "^6.0.0", - "decamelize": "^4.0.0", - "flat": "^5.0.2", - "is-plain-obj": "^2.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs-unparser/node_modules/is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - } - }, - "dependencies": { - "@sinonjs/commons": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", - "integrity": "sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==", - "dev": true, - "requires": { - "type-detect": "4.0.8" - } - }, - "@sinonjs/fake-timers": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz", - "integrity": "sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1.7.0" - } - }, - "@sinonjs/samsam": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-5.3.1.tgz", - "integrity": "sha512-1Hc0b1TtyfBu8ixF/tpfSHTVWKwCBLY4QJbkgnE7HcwyvT2xArDxb4K7dMgqRm3szI+LJbzmW/s4xxEhv6hwDg==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1.6.0", - "lodash.get": "^4.4.2", - "type-detect": "^4.0.8" - } - }, - "@sinonjs/text-encoding": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz", - "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", - "dev": true - }, - "@tootallnate/once": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", - "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", - "dev": true - }, - "abab": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", - "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", - "dev": true - }, - "acorn": { - "version": "8.8.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", - "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", - "dev": true - }, - "acorn-globals": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", - "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", - "dev": true, - "requires": { - "acorn": "^7.1.1", - "acorn-walk": "^7.1.1" - }, - "dependencies": { - "acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true - } - } - }, - "acorn-walk": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", - "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", - "dev": true - }, - "agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, - "requires": { - "debug": "4" - } - }, - "ansi-colors": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", - "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", - "dev": true - }, - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - } - }, - "assertion-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", - "dev": true - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", - "dev": true - }, - "balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "dev": true - }, - "braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "requires": { - "fill-range": "^7.1.1" - } - }, - "browser-process-hrtime": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", - "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", - "dev": true - }, - "browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", - "dev": true - }, - "camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true - }, - "chai": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.6.tgz", - "integrity": "sha512-bbcp3YfHCUzMOvKqsztczerVgBKSsEijCySNlHHbX3VG1nskvqjz5Rfso1gGwD6w6oOV3eI60pKuMOV5MV7p3Q==", - "dev": true, - "requires": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.2", - "deep-eql": "^3.0.1", - "get-func-name": "^2.0.0", - "loupe": "^2.3.1", - "pathval": "^1.1.1", - "type-detect": "^4.0.5" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "check-error": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", - "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", - "dev": true - }, - "chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, - "requires": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "fsevents": "~2.3.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" - } - }, - "cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "requires": { - "delayed-stream": "~1.0.0" - } - }, - "cssom": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz", - "integrity": "sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==", - "dev": true - }, - "cssstyle": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", - "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", - "dev": true, - "requires": { - "cssom": "~0.3.6" - }, - "dependencies": { - "cssom": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", - "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", - "dev": true - } - } - }, - "data-urls": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz", - "integrity": "sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==", - "dev": true, - "requires": { - "abab": "^2.0.3", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^8.0.0" - } - }, - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "decamelize": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", - "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", - "dev": true - }, - "decimal.js": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", - "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==", - "dev": true - }, - "deep-eql": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", - "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", - "dev": true, - "requires": { - "type-detect": "^4.0.0" - } - }, - "deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "dev": true - }, - "diff": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", - "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", - "dev": true - }, - "domexception": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz", - "integrity": "sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==", - "dev": true, - "requires": { - "webidl-conversions": "^5.0.0" - }, - "dependencies": { - "webidl-conversions": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz", - "integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==", - "dev": true - } - } - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "escalade": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", - "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", - "dev": true - }, - "escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true - }, - "escodegen": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz", - "integrity": "sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==", - "dev": true, - "requires": { - "esprima": "^4.0.1", - "estraverse": "^5.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1", - "source-map": "~0.6.1" - } - }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true - }, - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true - }, - "esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", - "dev": true - }, - "fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "requires": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - } - }, - "flat": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", - "dev": true - }, - "form-data": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", - "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", - "dev": true, - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true - }, - "fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "optional": true - }, - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true - }, - "get-func-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", - "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", - "dev": true - }, - "glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - }, - "dependencies": { - "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0" - } - }, - "minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "dev": true, - "requires": { - "brace-expansion": "^2.0.1" - } - } - } - }, - "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true - }, - "html-encoding-sniffer": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", - "integrity": "sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==", - "dev": true, - "requires": { - "whatwg-encoding": "^1.0.5" - } - }, - "http-proxy-agent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", - "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", - "dev": true, - "requires": { - "@tootallnate/once": "1", - "agent-base": "6", - "debug": "4" - } - }, - "https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "dev": true, - "requires": { - "agent-base": "6", - "debug": "4" - } - }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "requires": { - "binary-extensions": "^2.0.0" - } - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "is-potential-custom-element-name": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", - "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", - "dev": true - }, - "is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "dev": true - }, - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", - "dev": true - }, - "jsdom": { - "version": "16.7.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.7.0.tgz", - "integrity": "sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw==", - "dev": true, - "requires": { - "abab": "^2.0.5", - "acorn": "^8.2.4", - "acorn-globals": "^6.0.0", - "cssom": "^0.4.4", - "cssstyle": "^2.3.0", - "data-urls": "^2.0.0", - "decimal.js": "^10.2.1", - "domexception": "^2.0.1", - "escodegen": "^2.0.0", - "form-data": "^3.0.0", - "html-encoding-sniffer": "^2.0.1", - "http-proxy-agent": "^4.0.1", - "https-proxy-agent": "^5.0.0", - "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.0", - "parse5": "6.0.1", - "saxes": "^5.0.1", - "symbol-tree": "^3.2.4", - "tough-cookie": "^4.0.0", - "w3c-hr-time": "^1.0.2", - "w3c-xmlserializer": "^2.0.0", - "webidl-conversions": "^6.1.0", - "whatwg-encoding": "^1.0.5", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^8.5.0", - "ws": "^7.4.6", - "xml-name-validator": "^3.0.0" - } - }, - "jsdom-global": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/jsdom-global/-/jsdom-global-3.0.2.tgz", - "integrity": "sha1-a9KZwTsMRiay2iwDk81DhdYGrLk=", - "dev": true - }, - "just-extend": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.2.1.tgz", - "integrity": "sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==", - "dev": true - }, - "levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - } - }, - "locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "requires": { - "p-locate": "^5.0.0" - } - }, - "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, - "lodash.get": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", - "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", - "dev": true - }, - "log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dev": true, - "requires": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - } - }, - "loupe": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.1.tgz", - "integrity": "sha512-EN1D3jyVmaX4tnajVlfbREU4axL647hLec1h/PXAb8CPDMJiYitcWF2UeLVNttRqaIqQs4x+mRvXf+d+TlDrCA==", - "dev": true, - "requires": { - "get-func-name": "^2.0.0" - } - }, - "mime-db": { - "version": "1.51.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz", - "integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==", - "dev": true - }, - "mime-types": { - "version": "2.1.34", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz", - "integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==", - "dev": true, - "requires": { - "mime-db": "1.51.0" - } - }, - "mocha": { - "version": "10.7.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.7.0.tgz", - "integrity": "sha512-v8/rBWr2VO5YkspYINnvu81inSz2y3ODJrhO175/Exzor1RcEZZkizgE2A+w/CAXXoESS8Kys5E62dOHGHzULA==", - "dev": true, - "requires": { - "ansi-colors": "^4.1.3", - "browser-stdout": "^1.3.1", - "chokidar": "^3.5.3", - "debug": "^4.3.5", - "diff": "^5.2.0", - "escape-string-regexp": "^4.0.0", - "find-up": "^5.0.0", - "glob": "^8.1.0", - "he": "^1.2.0", - "js-yaml": "^4.1.0", - "log-symbols": "^4.1.0", - "minimatch": "^5.1.6", - "ms": "^2.1.3", - "serialize-javascript": "^6.0.2", - "strip-json-comments": "^3.1.1", - "supports-color": "^8.1.1", - "workerpool": "^6.5.1", - "yargs": "^16.2.0", - "yargs-parser": "^20.2.9", - "yargs-unparser": "^2.0.0" - }, - "dependencies": { - "argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0" - } - }, - "debug": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", - "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", - "dev": true, - "requires": { - "ms": "2.1.2" - }, - "dependencies": { - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - } - } - }, - "js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "requires": { - "argparse": "^2.0.1" - } - }, - "minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "dev": true, - "requires": { - "brace-expansion": "^2.0.1" - } - }, - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - }, - "supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "nise": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/nise/-/nise-4.1.0.tgz", - "integrity": "sha512-eQMEmGN/8arp0xsvGoQ+B1qvSkR73B1nWSCh7nOt5neMCtwcQVYQGdzQMhcNscktTsWB54xnlSQFzOAPJD8nXA==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1.7.0", - "@sinonjs/fake-timers": "^6.0.0", - "@sinonjs/text-encoding": "^0.7.1", - "just-extend": "^4.0.2", - "path-to-regexp": "^1.7.0" - } - }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true - }, - "nwsapi": { - "version": "2.2.7", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.7.tgz", - "integrity": "sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ==", - "dev": true - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "requires": { - "wrappy": "1" - } - }, - "optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "dev": true, - "requires": { - "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" - } - }, - "p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "requires": { - "yocto-queue": "^0.1.0" - } - }, - "p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "requires": { - "p-limit": "^3.0.2" - } - }, - "parse5": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", - "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", - "dev": true - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - }, - "path-to-regexp": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", - "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", - "dev": true, - "requires": { - "isarray": "0.0.1" - } - }, - "pathval": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", - "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", - "dev": true - }, - "picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true - }, - "prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", - "dev": true - }, - "psl": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", - "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==", - "dev": true - }, - "punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true - }, - "querystringify": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", - "dev": true - }, - "randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "requires": { - "safe-buffer": "^5.1.0" - } - }, - "readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "requires": { - "picomatch": "^2.2.1" - } - }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true - }, - "requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", - "dev": true - }, - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true - }, - "saxes": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", - "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", - "dev": true, - "requires": { - "xmlchars": "^2.2.0" - } - }, - "serialize-javascript": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", - "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", - "dev": true, - "requires": { - "randombytes": "^2.1.0" - } - }, - "sinon": { - "version": "9.2.4", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-9.2.4.tgz", - "integrity": "sha512-zljcULZQsJxVra28qIAL6ow1Z9tpattkCTEJR4RBP3TGc00FcttsP5pK284Nas5WjMZU5Yzy3kAIp3B3KRf5Yg==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1.8.1", - "@sinonjs/fake-timers": "^6.0.1", - "@sinonjs/samsam": "^5.3.1", - "diff": "^4.0.2", - "nise": "^4.0.4", - "supports-color": "^7.1.0" - }, - "dependencies": { - "diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true - } - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "optional": true - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - }, - "strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "symbol-tree": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", - "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", - "dev": true - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - }, - "tough-cookie": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", - "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", - "dev": true, - "requires": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.2.0", - "url-parse": "^1.5.3" - }, - "dependencies": { - "universalify": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", - "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", - "dev": true - } - } - }, - "tr46": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz", - "integrity": "sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==", - "dev": true, - "requires": { - "punycode": "^2.1.1" - } - }, - "type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2" - } - }, - "type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true - }, - "url-parse": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", - "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", - "dev": true, - "requires": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" - } - }, - "w3c-hr-time": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", - "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", - "dev": true, - "requires": { - "browser-process-hrtime": "^1.0.0" - } - }, - "w3c-xmlserializer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz", - "integrity": "sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==", - "dev": true, - "requires": { - "xml-name-validator": "^3.0.0" - } - }, - "webidl-conversions": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", - "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==", - "dev": true - }, - "whatwg-encoding": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", - "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", - "dev": true, - "requires": { - "iconv-lite": "0.4.24" - } - }, - "whatwg-mimetype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", - "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==", - "dev": true - }, - "whatwg-url": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.7.0.tgz", - "integrity": "sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg==", - "dev": true, - "requires": { - "lodash": "^4.7.0", - "tr46": "^2.1.0", - "webidl-conversions": "^6.1.0" - } - }, - "word-wrap": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", - "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", - "dev": true - }, - "workerpool": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz", - "integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==", - "dev": true - }, - "wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true - }, - "ws": { - "version": "7.5.10", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", - "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", - "dev": true - }, - "xml-name-validator": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", - "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", - "dev": true - }, - "xmlchars": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", - "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", - "dev": true - }, - "y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true - }, - "yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, - "requires": { - "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-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "dev": true - }, - "yargs-unparser": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", - "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", - "dev": true, - "requires": { - "camelcase": "^6.0.0", - "decamelize": "^4.0.0", - "flat": "^5.0.2", - "is-plain-obj": "^2.1.0" - }, - "dependencies": { - "is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", - "dev": true - } - } - }, - "yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true - } - } -} \ No newline at end of file diff --git a/plugins/keyboard-navigation/package.json b/plugins/keyboard-navigation/package.json deleted file mode 100644 index 0193fb9af..000000000 --- a/plugins/keyboard-navigation/package.json +++ /dev/null @@ -1,60 +0,0 @@ -{ - "name": "@blockly/keyboard-navigation", - "version": "0.6.11", - "description": "A Blockly plugin that adds keyboard navigation support.", - "scripts": { - "audit:fix": "blockly-scripts auditFix", - "build": "blockly-scripts build", - "clean": "blockly-scripts clean", - "lint": "eslint .", - "predeploy": "blockly-scripts predeploy", - "start": "blockly-scripts start", - "test": "blockly-scripts test" - }, - "main": "./dist/index.js", - "module": "./src/index.js", - "unpkg": "./dist/index.js", - "author": "Blockly Team", - "keywords": [ - "blockly", - "blockly-plugin", - "keyboard-navigation" - ], - "homepage": "https://github.com/google/blockly-samples/tree/master/plugins/keyboard-navigation#readme", - "bugs": { - "url": "https://github.com/google/blockly-samples/issues" - }, - "repository": { - "type": "git", - "url": "https://github.com/google/blockly-samples.git", - "directory": "plugins/keyboard-navigation" - }, - "license": "Apache-2.0", - "directories": { - "dist": "dist", - "src": "src" - }, - "files": [ - "dist", - "src" - ], - "devDependencies": { - "@blockly/dev-scripts": "^4.0.6", - "@blockly/dev-tools": "^8.0.11", - "chai": "^4.2.0", - "jsdom": "^16.4.0", - "jsdom-global": "^3.0.2", - "mocha": "^10.7.0", - "sinon": "^9.0.1" - }, - "peerDependencies": { - "blockly": "^11.0.0" - }, - "publishConfig": { - "access": "public", - "registry": "https://wombat-dressing-room.appspot.com" - }, - "engines": { - "node": ">=8.17.0" - } -} diff --git a/plugins/keyboard-navigation/src/constants.js b/plugins/keyboard-navigation/src/constants.js deleted file mode 100644 index 187ad47dc..000000000 --- a/plugins/keyboard-navigation/src/constants.js +++ /dev/null @@ -1,63 +0,0 @@ -/** - * @license - * Copyright 2021 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -/** - * @fileoverview Constants for keyboard navigation. - * @author aschmiedt@google.com (Abby Schmiedt) - */ - -/** - * Keyboard navigation states. - * The different parts of Blockly that the user navigates between. - * @enum {string} - * @const - * @public - */ -export const STATE = { - WORKSPACE: 'workspace', - FLYOUT: 'flyout', - TOOLBOX: 'toolbox', -}; - -/** - * Default keyboard navigation shortcut names. - * @enum {string} - * @const - * @public - */ -export const SHORTCUT_NAMES = { - PREVIOUS: 'previous', - NEXT: 'next', - IN: 'in', - OUT: 'out', - INSERT: 'insert', - MARK: 'mark', - DISCONNECT: 'disconnect', - TOOLBOX: 'toolbox', - EXIT: 'exit', - TOGGLE_KEYBOARD_NAV: 'toggle_keyboard_nav', - COPY: 'keyboard_nav_copy', - CUT: 'keyboard_nav_cut', - PASTE: 'keyboard_nav_paste', - DELETE: 'keyboard_nav_delete', - MOVE_WS_CURSOR_UP: 'workspace_up', - MOVE_WS_CURSOR_DOWN: 'workspace_down', - MOVE_WS_CURSOR_LEFT: 'workspace_left', - MOVE_WS_CURSOR_RIGHT: 'workspace_right', -}; - -/** - * Types of possible messages passed into the loggingCallback in the Navigation - * class. - * @enum {string} - * @const - * @public - */ -export const LOGGING_MSG_TYPE = { - ERROR: 'error', - WARN: 'warn', - LOG: 'log', -}; diff --git a/plugins/keyboard-navigation/src/flyout_cursor.js b/plugins/keyboard-navigation/src/flyout_cursor.js deleted file mode 100644 index 20cd17b95..000000000 --- a/plugins/keyboard-navigation/src/flyout_cursor.js +++ /dev/null @@ -1,93 +0,0 @@ -/** - * @license - * Copyright 2021 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -/** - * @fileoverview The class representing a cursor used to navigate the flyout. - * @author aschmiedt@google.com (Abby Schmiedt) - */ - -import * as Blockly from 'blockly/core'; - -/** - * Class for a flyout cursor. - * This controls how a user navigates blocks in the flyout. - * This cursor only allows a user to go to the previous or next stack. - * @constructor - * @extends {Blockly.Cursor} - */ -export class FlyoutCursor extends Blockly.Cursor { - /** - * The constructor for the FlyoutCursor. - */ - constructor() { - super(); - } - - /** - * Moves the cursor to the next stack of blocks in the flyout. - * @returns {Blockly.ASTNode} The next element, or null if the current node is - * not set or there is no next value. - * @override - */ - next() { - const curNode = this.getCurNode(); - if (!curNode) { - return null; - } - const newNode = curNode.next(); - - if (newNode) { - this.setCurNode(newNode); - } - return newNode; - } - - /** - * This is a no-op since a flyout cursor can not go in. - * @returns {null} Always null. - * @override - */ - in() { - return null; - } - - /** - * Moves the cursor to the previous stack of blocks in the flyout. - * @returns {Blockly.ASTNode} The previous element, or null if the current - * node is not set or there is no previous value. - * @override - */ - prev() { - const curNode = this.getCurNode(); - if (!curNode) { - return null; - } - const newNode = curNode.prev(); - - if (newNode) { - this.setCurNode(newNode); - } - return newNode; - } - - /** - * This is a no-op since a flyout cursor can not go out. - * @returns {null} Always null. - * @override - */ - out() { - return null; - } -} - -export const registrationType = Blockly.registry.Type.CURSOR; -export const registrationName = 'FlyoutCursor'; - -Blockly.registry.register(registrationType, registrationName, FlyoutCursor); - -export const pluginInfo = { - [registrationType]: registrationName, -}; diff --git a/plugins/keyboard-navigation/src/gesture_monkey_patch.js b/plugins/keyboard-navigation/src/gesture_monkey_patch.js deleted file mode 100644 index ba5d6eb79..000000000 --- a/plugins/keyboard-navigation/src/gesture_monkey_patch.js +++ /dev/null @@ -1,59 +0,0 @@ -/** - * @license - * Copyright 2021 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -/** - * @fileoverview Overrides methods on Blockly.Gesture in order to allow users - * to move the cursor to blocks or the workspace using shift click. - * TODO(google/blockly#4584): We do not have a way to do this currently without - * monkey patching Blockly. - * @author aschmiedt@google.com (Abby Schmiedt) - */ - -import * as Blockly from 'blockly/core'; - -const oldDoWorkspaceClick = Blockly.Gesture.prototype.doWorkspaceClick_; - -/** - * Execute a workspace click. When in accessibility mode shift clicking will - * move the cursor. - * @param {!Event} e A mouse up or touch end event. - * @this {Blockly.Gesture} - * @override - */ -Blockly.Gesture.prototype.doWorkspaceClick_ = function (e) { - oldDoWorkspaceClick.call(this, e); - const ws = this.creatorWorkspace_; - if (e.shiftKey && ws.keyboardAccessibilityMode) { - const screenCoord = new Blockly.utils.Coordinate(e.clientX, e.clientY); - const wsCoord = Blockly.utils.svgMath.screenToWsCoordinates( - ws, - screenCoord, - ); - const wsNode = Blockly.ASTNode.createWorkspaceNode(ws, wsCoord); - ws.getCursor().setCurNode(wsNode); - } -}; - -const oldDoBlockClick = Blockly.Gesture.prototype.doBlockClick_; - -/** - * Execute a block click. When in accessibility mode shift clicking will move - * the cursor to the block. - * @this {Blockly.Gesture} - * @override - */ -Blockly.Gesture.prototype.doBlockClick_ = function (e) { - oldDoBlockClick.call(this, e); - if ( - !this.targetBlock_.isInFlyout && - this.mostRecentEvent_.shiftKey && - this.targetBlock_.workspace.keyboardAccessibilityMode - ) { - this.creatorWorkspace_ - .getCursor() - .setCurNode(Blockly.ASTNode.createTopNode(this.targetBlock_)); - } -}; diff --git a/plugins/keyboard-navigation/src/index.js b/plugins/keyboard-navigation/src/index.js deleted file mode 100644 index 75ef7e015..000000000 --- a/plugins/keyboard-navigation/src/index.js +++ /dev/null @@ -1,25 +0,0 @@ -/** - * @license - * Copyright 2021 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as Constants from '../src/constants'; - -import { - FlyoutCursor, - pluginInfo as FlyoutCursorPluginInfo, -} from './flyout_cursor'; -import {LineCursor, pluginInfo as LineCursorPluginInfo} from './line_cursor'; -import {Navigation} from './navigation'; -import {NavigationController} from './navigation_controller'; - -export { - Constants, - FlyoutCursor, - FlyoutCursorPluginInfo, - LineCursor, - LineCursorPluginInfo, - Navigation, - NavigationController, -}; diff --git a/plugins/keyboard-navigation/src/line_cursor.js b/plugins/keyboard-navigation/src/line_cursor.js deleted file mode 100644 index 8f1595b92..000000000 --- a/plugins/keyboard-navigation/src/line_cursor.js +++ /dev/null @@ -1,190 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -/** - * @fileoverview The class representing a line cursor. - * A line cursor tries to traverse the blocks and connections on a block as if - * they were lines of code in a text editor. Previous and next traverse previous - * connections, next connections and blocks, while in and out traverse input - * connections and fields. - * @author aschmiedt@google.com (Abby Schmiedt) - */ - -import * as Blockly from 'blockly/core'; - -/** - * Class for a line cursor. - * @constructor - * @extends {Blockly.BasicCursor} - */ -export class LineCursor extends Blockly.BasicCursor { - /** - * Constructor for a line cursor. - */ - constructor() { - super(); - } - - /** - * Moves the cursor to the next previous connection, next connection or block - * in the pre order traversal. Finds the next node in the pre order traversal. - * @returns {Blockly.ASTNode} The next node, or null if the current node is - * not set or there is no next value. - * @override - */ - next() { - const curNode = this.getCurNode(); - if (!curNode) { - return null; - } - let newNode = this.getNextNode_(curNode, this.validLineNode); - - // Skip the input or next value if there is a connected block. - if ( - newNode && - (newNode.getType() == Blockly.ASTNode.types.INPUT || - newNode.getType() == Blockly.ASTNode.types.NEXT) && - newNode.getLocation().targetBlock() - ) { - newNode = this.getNextNode_(newNode, this.validLineNode); - } - if (newNode) { - this.setCurNode(newNode); - } - return newNode; - } - - /** - * Moves the cursor to the next input connection or field - * in the pre order traversal. - * @returns {Blockly.ASTNode} The next node, or null if the current node is - * not set or there is no next value. - * @override - */ - in() { - const curNode = this.getCurNode(); - if (!curNode) { - return null; - } - const newNode = this.getNextNode_(curNode, this.validInLineNode); - - if (newNode) { - this.setCurNode(newNode); - } - return newNode; - } - /** - * Moves the cursor to the previous next connection or previous connection in - * the pre order traversal. - * @returns {Blockly.ASTNode} The previous node, or null if the current node - * is not set or there is no previous value. - * @override - */ - prev() { - const curNode = this.getCurNode(); - if (!curNode) { - return null; - } - let newNode = this.getPreviousNode_(curNode, this.validLineNode); - - if ( - newNode && - (newNode.getType() == Blockly.ASTNode.types.INPUT || - newNode.getType() == Blockly.ASTNode.types.NEXT) && - newNode.getLocation().targetBlock() - ) { - newNode = this.getPreviousNode_(newNode, this.validLineNode); - } - - if (newNode) { - this.setCurNode(newNode); - } - return newNode; - } - /** - * Moves the cursor to the previous input connection or field in the pre order - * traversal. - * @returns {Blockly.ASTNode} The previous node, or null if the current node - * is not set or there is no previous value. - * @override - */ - out() { - const curNode = this.getCurNode(); - if (!curNode) { - return null; - } - const newNode = this.getPreviousNode_(curNode, this.validInLineNode); - - if (newNode) { - this.setCurNode(newNode); - } - return newNode; - } - - /** - * Decides if the previous and next methods should traverse the given node. - * The previous and next method only traverse previous connections, next - * connections and blocks. - * @param {Blockly.ASTNode} node The AST node to check. - * @returns {boolean} True if the node should be visited, false otherwise. - * @protected - */ - validLineNode(node) { - if (!node) { - return false; - } - let isValid = false; - const location = node.getLocation(); - const type = node && node.getType(); - if (type == Blockly.ASTNode.types.BLOCK) { - if (location.outputConnection === null) { - isValid = true; - } - } else if ( - type == Blockly.ASTNode.types.INPUT && - location.type == Blockly.NEXT_STATEMENT - ) { - isValid = true; - } else if (type == Blockly.ASTNode.types.NEXT) { - isValid = true; - } - return isValid; - } - - /** - * Decides if the in and out methods should traverse the given node. - * The in and out method only traverse fields and input connections. - * @param {Blockly.ASTNode} node The AST node to check whether it is valid. - * @returns {boolean} True if the node should be visited, false otherwise. - * @protected - */ - validInLineNode(node) { - if (!node) { - return false; - } - let isValid = false; - const location = node.getLocation(); - const type = node && node.getType(); - if (type == Blockly.ASTNode.types.FIELD) { - isValid = true; - } else if ( - type == Blockly.ASTNode.types.INPUT && - location.type == Blockly.INPUT_VALUE - ) { - isValid = true; - } - return isValid; - } -} - -export const registrationName = 'LineCursor'; -export const registrationType = Blockly.registry.Type.CURSOR; - -Blockly.registry.register(registrationType, registrationName, LineCursor); - -export const pluginInfo = { - [registrationType]: registrationName, -}; diff --git a/plugins/keyboard-navigation/src/navigation.js b/plugins/keyboard-navigation/src/navigation.js deleted file mode 100644 index e78edb897..000000000 --- a/plugins/keyboard-navigation/src/navigation.js +++ /dev/null @@ -1,1289 +0,0 @@ -/** - * @license - * Copyright 2021 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -/** - * @fileoverview Holds all methods necessary to use Blockly through the - * keyboard. - * @author aschmiedt@google.com (Abby Schmiedt) - */ - -import * as Blockly from 'blockly/core'; -import * as Constants from './constants'; -import { - registrationName as cursorRegistrationName, - registrationType as cursorRegistrationType, -} from './flyout_cursor'; - -/** - * Class that holds all methods necessary for keyboard navigation to work. - */ -export class Navigation { - /** - * Constructor for keyboard navigation. - */ - constructor() { - /** - * Object holding the location of the cursor for each workspace. - * Possible locations of the cursor are: workspace, flyout or toolbox. - * @type {Object} - * @protected - */ - this.workspaceStates = {}; - - /** - * An optional method that allows a developer to customize how to handle - * logs, warnings, and errors. The first argument is one of 'log', 'warn', - * or 'error'. The second argument is the message. - * @type {?function(Constants.LOGGING_MSG_TYPE, string)} - * @public - */ - this.loggingCallback = null; - - /** - * The distance to move the cursor when the cursor is on the workspace. - * @type {number} - * @public - */ - this.WS_MOVE_DISTANCE = 40; - - /** - * The name of the marker to use for keyboard navigation. - * @type {string} - * @public - */ - this.MARKER_NAME = 'local_marker_1'; - - /** - * The default coordinate to use when focusing on the workspace and no - * blocks are present. In pixel coordinates, but will be converted to - * workspace coordinates when used to position the cursor. - * @type {!Blockly.utils.Coordinate} - * @public - */ - this.DEFAULT_WS_COORDINATE = new Blockly.utils.Coordinate(100, 100); - - /** - * The default coordinate to use when moving the cursor to the workspace - * after a block has been deleted. In pixel coordinates, but will be - * converted to workspace coordinates when used to position the cursor. - * @type {!Blockly.utils.Coordinate} - * @public - */ - this.WS_COORDINATE_ON_DELETE = new Blockly.utils.Coordinate(100, 100); - - /** - * Wrapper for method that deals with workspace changes. - * Used for removing change listener. - * @type {Function} - * @protected - */ - this.wsChangeWrapper = this.workspaceChangeListener.bind(this); - - /** - * Wrapper for method that deals with flyout changes. - * Used for removing change listener. - * @type {Function} - * @protected - */ - this.flyoutChangeWrapper = this.flyoutChangeListener.bind(this); - - /** - * The list of registered workspaces. - * Used when removing change listeners in dispose. - * @type {!Array} - * @protected - */ - this.workspaces = []; - } - - /** - * Adds all necessary change listeners and markers to a workspace for keyboard - * navigation to work. This must be called for keyboard navigation to work - * on a workspace. - * @param {!Blockly.WorkspaceSvg} workspace The workspace to add keyboard - * navigation to. - * @public - */ - addWorkspace(workspace) { - this.workspaces.push(workspace); - const flyout = workspace.getFlyout(); - workspace - .getMarkerManager() - .registerMarker(this.MARKER_NAME, new Blockly.Marker()); - workspace.addChangeListener(this.wsChangeWrapper); - - if (flyout) { - this.addFlyout(flyout); - } - } - - /** - * Removes all keyboard navigation change listeners and markers. - * @param {!Blockly.WorkspaceSvg} workspace The workspace to remove keyboard - * navigation from. - * @public - */ - removeWorkspace(workspace) { - const workspaceIdx = this.workspaces.indexOf(workspace); - const flyout = workspace.getFlyout(); - - if (workspace.getCursor()) { - this.disableKeyboardAccessibility(workspace); - } - - if (workspaceIdx > -1) { - this.workspaces.splice(workspaceIdx, 1); - } - if (workspace.getMarkerManager()) { - workspace.getMarkerManager().unregisterMarker(this.MARKER_NAME); - } - workspace.removeChangeListener(this.wsChangeWrapper); - - if (flyout) { - this.removeFlyout(flyout); - } - } - - /** - * Sets the state for the given workspace. - * @param {!Blockly.WorkspaceSvg} workspace The workspace to set the state on. - * @param {!Constants.STATE} state The navigation state. - * @protected - */ - setState(workspace, state) { - this.workspaceStates[workspace.id] = state; - } - - /** - * Gets the navigation state of the current workspace. - * @param {!Blockly.WorkspaceSvg} workspace The workspace to get the state of. - * @returns {!Constants.STATE} The state of the given workspace. - * @package - */ - getState(workspace) { - return this.workspaceStates[workspace.id]; - } - - /** - * Gets the marker created for keyboard navigation. - * @param {!Blockly.WorkspaceSvg} workspace The workspace to get the marker - * from. - * @returns {?Blockly.Marker} The marker created for keyboard navigation. - * @protected - */ - getMarker(workspace) { - return workspace.getMarker(this.MARKER_NAME); - } - - /** - * Adds all event listeners and cursors to the flyout that are needed for - * keyboard navigation to work. - * @param {!Blockly.IFlyout} flyout The flyout to add a cursor and change - * listeners to. - * @protected - */ - addFlyout(flyout) { - const flyoutWorkspace = flyout.getWorkspace(); - flyoutWorkspace.addChangeListener(this.flyoutChangeWrapper); - const FlyoutCursorClass = Blockly.registry.getClass( - cursorRegistrationType, - cursorRegistrationName, - ); - flyoutWorkspace.getMarkerManager().setCursor(new FlyoutCursorClass()); - } - - /** - * Removes all change listeners from the flyout that are needed for - * keyboard navigation to work. - * @param {!Blockly.IFlyout} flyout The flyout to add a cursor and event - * listeners to. - * @protected - */ - removeFlyout(flyout) { - const flyoutWorkspace = flyout.getWorkspace(); - flyoutWorkspace.removeChangeListener(this.flyoutChangeWrapper); - } - - /** - * Updates the state of keyboard navigation and the position of the cursor - * based on workspace events. - * @param {!Blockly.Events.Abstract} e The Blockly event to process. - * @protected - */ - workspaceChangeListener(e) { - const workspace = Blockly.Workspace.getById(e.workspaceId); - if (!workspace || !workspace.keyboardAccessibilityMode) { - return; - } - switch (e.type) { - case Blockly.Events.DELETE: - this.handleBlockDeleteByDrag(workspace, e); - break; - case Blockly.Events.BLOCK_CHANGE: - if (e.element === 'mutation') { - this.handleBlockMutation( - workspace, - /** @type {Blockly.Events.BlockChange} */ (e), - ); - } - break; - case Blockly.Events.CLICK: - this.handleWorkspaceClick( - workspace, - /** @type {Blockly.Events.Click} */ (e), - ); - break; - case Blockly.Events.TOOLBOX_ITEM_SELECT: - this.handleToolboxCategoryClick( - workspace, - /** @type {Blockly.Events.ToolboxItemSelect} */ (e), - ); - break; - case Blockly.Events.BLOCK_CREATE: - this.handleBlockCreate(workspace, e); - } - } - - /** - * Updates the state of keyboard navigation and the position of the cursor - * based on events emitted from the flyout's workspace. - * @param {!Blockly.Events.Abstract} e The Blockly event to process. - * @protected - */ - flyoutChangeListener(e) { - const flyoutWorkspace = Blockly.Workspace.getById(e.workspaceId); - const mainWorkspace = flyoutWorkspace.targetWorkspace; - const flyout = mainWorkspace.getFlyout(); - - // This is called for simple toolboxes and for toolboxes that have a flyout - // that does not close. Autoclosing flyouts close before we need to focus - // the cursor on the block that was clicked. - if ( - mainWorkspace && - mainWorkspace.keyboardAccessibilityMode && - !flyout.autoClose - ) { - if (e.type === Blockly.Events.CLICK && e.targetType === 'block') { - const block = flyoutWorkspace.getBlockById(e.blockId); - this.handleBlockClickInFlyout(mainWorkspace, block); - } else if (e.type === Blockly.Events.SELECTED) { - const block = flyoutWorkspace.getBlockById(e.newElementId); - this.handleBlockClickInFlyout(mainWorkspace, block); - } - } - } - - /** - * Moves the cursor to the workspace if a block has been dragged from a simple - * toolbox. For a category toolbox this is handled in - * handleToolboxCategoryClick_. - * @param {!Blockly.WorkspaceSvg} workspace The workspace the cursor belongs - * to. - * @param {!Blockly.Events.Abstract} e The Blockly event to process. - * @protected - */ - handleBlockCreate(workspace, e) { - if (this.getState(workspace) === Constants.STATE.FLYOUT) { - this.resetFlyout(workspace, !!workspace.getToolbox()); - this.setState(workspace, Constants.STATE.WORKSPACE); - } - } - - /** - * Moves the cursor to the block level when the block the cursor is on - * mutates. - * @param {!Blockly.WorkspaceSvg} workspace The workspace the cursor belongs - * to. - * @param {!Blockly.Events.BlockChange} e The Blockly event to process. - * @protected - */ - handleBlockMutation(workspace, e) { - const mutatedBlockId = e.blockId; - const cursor = workspace.getCursor(); - if (cursor) { - const curNode = cursor.getCurNode(); - const block = curNode ? curNode.getSourceBlock() : null; - if (block && block.id === mutatedBlockId) { - cursor.setCurNode(Blockly.ASTNode.createBlockNode(block)); - } - } - } - - /** - * Moves the cursor to the workspace when a user clicks on the workspace. - * @param {!Blockly.WorkspaceSvg} workspace The workspace the cursor belongs - * to. - * @param {!Blockly.Events.Click} e The Blockly event to process. - * @protected - */ - handleWorkspaceClick(workspace, e) { - const workspaceState = this.getState(workspace); - if (workspaceState !== Constants.STATE.WORKSPACE) { - this.resetFlyout(workspace, !!workspace.getToolbox()); - this.setState(workspace, Constants.STATE.WORKSPACE); - } - } - - /** - * Moves the cursor to the toolbox when a user clicks on a toolbox category. - * Moves the cursor to the workspace if theh user closes the toolbox category. - * @param {!Blockly.WorkspaceSvg} workspace The workspace the toolbox is on. - * @param {!Blockly.Events.ToolboxItemSelect} e The event emitted from the - * workspace. - * @protected - */ - handleToolboxCategoryClick(workspace, e) { - const workspaceState = this.getState(workspace); - if (e.newItem && workspaceState !== Constants.STATE.TOOLBOX) { - // If the toolbox category was just clicked, focus on the toolbox. - this.focusToolbox(workspace); - } else if (!e.newItem) { - // If the toolbox was closed, focus on the workspace. - this.resetFlyout(workspace, !!workspace.getToolbox()); - this.setState(workspace, Constants.STATE.WORKSPACE); - } - } - - /** - * Moves the cursor to the workspace when its parent block is deleted by - * being dragged to the flyout or to the trashcan. - * @param {!Blockly.WorkspaceSvg} workspace The workspace the block was on. - * @param {!Blockly.Events.Delete} e The event emitted when a block is - * deleted. - * @protected - */ - handleBlockDeleteByDrag(workspace, e) { - const deletedBlockId = e.blockId; - const ids = e.ids; - const cursor = workspace.getCursor(); - - // Make sure the cursor is on a block. - if ( - !cursor || - !cursor.getCurNode() || - !cursor.getCurNode().getSourceBlock() - ) { - return; - } - - const curNode = cursor.getCurNode(); - const sourceBlock = curNode.getSourceBlock(); - if (sourceBlock.id === deletedBlockId || ids.indexOf(sourceBlock.id) > -1) { - cursor.setCurNode( - Blockly.ASTNode.createWorkspaceNode( - workspace, - this.WS_COORDINATE_ON_DELETE, - ), - ); - } - } - - /** - * Handles when a user clicks on a block in the flyout by moving the cursor - * to that stack of blocks and setting the state of navigation to the flyout. - * @param {!Blockly.WorkspaceSvg} mainWorkspace The workspace the user clicked - * on. - * @param {!Blockly.BlockSvg} block The block the user clicked on. - * @protected - */ - handleBlockClickInFlyout(mainWorkspace, block) { - if (!block) { - return; - } - if (block.isShadow()) { - block = /** @type {Blockly.BlockSvg}*/ (block.getParent()); - } - this.getFlyoutCursor(mainWorkspace).setCurNode( - Blockly.ASTNode.createStackNode(block), - ); - this.setState(mainWorkspace, Constants.STATE.FLYOUT); - } - - /** - * Moves the cursor to the appropriate location before a block is deleted. - * This is used when the user deletes a block using the delete or backspace - * key. - * @param {!Blockly.WorkspaceSvg} workspace The workspace the block is being - * deleted on. - * @param {!Blockly.BlockSvg} deletedBlock The block that is being deleted. - * @package - */ - moveCursorOnBlockDelete(workspace, deletedBlock) { - if (!workspace || !workspace.getCursor()) { - return; - } - const cursor = workspace.getCursor(); - const curNode = cursor.getCurNode(); - const block = curNode ? curNode.getSourceBlock() : null; - - if (block === deletedBlock) { - // If the block has a parent move the cursor to their connection point. - if (block.getParent()) { - const topConnection = - block.previousConnection || block.outputConnection; - if (topConnection) { - cursor.setCurNode( - Blockly.ASTNode.createConnectionNode( - topConnection.targetConnection, - ), - ); - } - } else { - // If the block is by itself move the cursor to the workspace. - cursor.setCurNode( - Blockly.ASTNode.createWorkspaceNode( - block.workspace, - block.getRelativeToSurfaceXY(), - ), - ); - } - // If the cursor is on a block whose parent is being deleted, move the - // cursor to the workspace. - } else if (block && deletedBlock.getChildren(false).indexOf(block) > -1) { - cursor.setCurNode( - Blockly.ASTNode.createWorkspaceNode( - block.workspace, - block.getRelativeToSurfaceXY(), - ), - ); - } - } - - /** - * Sets the navigation state to toolbox and selects the first category in the - * toolbox. No-op if a toolbox does not exist on the given workspace. - * @param {!Blockly.WorkspaceSvg} workspace The workspace to get the toolbox - * on. - * @package - */ - focusToolbox(workspace) { - const toolbox = workspace.getToolbox(); - if (!toolbox) { - return; - } - - this.setState(workspace, Constants.STATE.TOOLBOX); - this.resetFlyout(workspace, false /* shouldHide */); - - if (!this.getMarker(workspace).getCurNode()) { - this.markAtCursor(workspace); - } - - if (!toolbox.getSelectedItem()) { - // Find the first item that is selectable. - const toolboxItems = toolbox.getToolboxItems(); - for (let i = 0, toolboxItem; (toolboxItem = toolboxItems[i]); i++) { - if (toolboxItem.isSelectable()) { - toolbox.selectItemByPosition(i); - break; - } - } - } - } - - /** - * Sets the navigation state to flyout and moves the cursor to the first - * block or button in the flyout. - * @param {!Blockly.WorkspaceSvg} workspace The workspace the flyout is on. - * @package - */ - focusFlyout(workspace) { - const flyout = workspace.getFlyout(); - - this.setState(workspace, Constants.STATE.FLYOUT); - - if (!this.getMarker(workspace).getCurNode()) { - this.markAtCursor(workspace); - } - - if (flyout && flyout.getWorkspace()) { - const flyoutContents = flyout.getContents(); - const firstFlyoutItem = flyoutContents[0]; - if (!firstFlyoutItem) return; - if (firstFlyoutItem.element instanceof Blockly.FlyoutButton) { - const astNode = Blockly.ASTNode.createButtonNode( - firstFlyoutItem.element, - ); - this.getFlyoutCursor(workspace).setCurNode(astNode); - } else if (firstFlyoutItem.element instanceof Blockly.BlockSvg) { - const astNode = Blockly.ASTNode.createStackNode( - firstFlyoutItem.element, - ); - this.getFlyoutCursor(workspace).setCurNode(astNode); - } - } - } - - /** - * Sets the navigation state to workspace and moves the cursor to either the - * top block on a workspace or to the workspace. - * @param {!Blockly.WorkspaceSvg} workspace The workspace to focus on. - * @package - */ - focusWorkspace(workspace) { - workspace.hideChaff(); - const reset = !!workspace.getToolbox(); - - this.resetFlyout(workspace, reset); - this.setState(workspace, Constants.STATE.WORKSPACE); - this.setCursorOnWorkspaceFocus(workspace); - } - - /** - * Moves the cursor to the top connection point on on the first top block. - * If the workspace is empty, moves the cursor to the default location on - * the workspace. - * @param {!Blockly.WorkspaceSvg} workspace The main Blockly workspace. - * @protected - */ - setCursorOnWorkspaceFocus(workspace) { - const topBlocks = workspace.getTopBlocks(true); - const cursor = workspace.getCursor(); - const wsCoordinates = new Blockly.utils.Coordinate( - this.DEFAULT_WS_COORDINATE.x / workspace.scale, - this.DEFAULT_WS_COORDINATE.y / workspace.scale, - ); - if (topBlocks.length > 0) { - cursor.setCurNode(Blockly.ASTNode.createTopNode(topBlocks[0])); - } else { - const wsNode = Blockly.ASTNode.createWorkspaceNode( - workspace, - wsCoordinates, - ); - cursor.setCurNode(wsNode); - } - } - - /** - * Gets the cursor on the flyout's workspace. - * @param {!Blockly.WorkspaceSvg} workspace The main workspace the flyout is - * on. - * @returns {?Blockly.FlyoutCursor} The flyout's cursor or null if no flyout - * exists. - * @protected - */ - getFlyoutCursor(workspace) { - const flyout = workspace.getFlyout(); - const cursor = flyout ? flyout.getWorkspace().getCursor() : null; - - return /** @type {?Blockly.FlyoutCursor} */ (cursor); - } - - /** - * Inserts a block from the flyout. - * Tries to find a connection on the block to connect to the marked - * location. If no connection has been marked, or there is not a compatible - * connection then the block is placed on the workspace. - * @param {!Blockly.WorkspaceSvg} workspace The main workspace. The workspace - * the block will be placed on. - * @package - */ - insertFromFlyout(workspace) { - const newBlock = this.createNewBlock(workspace); - if (!newBlock) { - return; - } - const markerNode = this.getMarker(workspace).getCurNode(); - if ( - !this.tryToConnectMarkerAndCursor( - workspace, - markerNode, - Blockly.ASTNode.createBlockNode(newBlock), - ) - ) { - this.warn( - 'Something went wrong while inserting a block from the flyout.', - ); - } - - this.focusWorkspace(workspace); - workspace.getCursor().setCurNode(Blockly.ASTNode.createTopNode(newBlock)); - this.removeMark(workspace); - } - - /** - * Creates a new block based on the current block the flyout cursor is on. - * @param {!Blockly.WorkspaceSvg} workspace The main workspace. The workspace - * the block will be placed on. - * @returns {?Blockly.BlockSvg} The newly created block. - * @protected - */ - createNewBlock(workspace) { - const flyout = workspace.getFlyout(); - if (!flyout || !flyout.isVisible()) { - this.warn( - 'Trying to insert from the flyout when the flyout does not ' + - ' exist or is not visible', - ); - return null; - } - - const curBlock = /** @type {!Blockly.BlockSvg} */ ( - this.getFlyoutCursor(workspace).getCurNode().getLocation() - ); - if (!curBlock.isEnabled()) { - this.warn("Can't insert a disabled block."); - return null; - } - - const newBlock = flyout.createBlock(curBlock); - // Render to get the sizing right. - newBlock.render(); - // Connections are not tracked when the block is first created. Normally - // there's enough time for them to become tracked in the user's mouse - // movements, but not here. - newBlock.setConnectionTracking(true); - return newBlock; - } - - /** - * Hides the flyout cursor and optionally hides the flyout. - * @param {!Blockly.WorkspaceSvg} workspace The workspace. - * @param {boolean} shouldHide True if the flyout should be hidden. - * @protected - */ - resetFlyout(workspace, shouldHide) { - if (this.getFlyoutCursor(workspace)) { - this.getFlyoutCursor(workspace).hide(); - if (shouldHide) { - workspace.getFlyout().hide(); - } - } - } - - /** - * Connects the location of the marker and the location of the cursor. - * No-op if the marker or cursor node are null. - * @param {!Blockly.WorkspaceSvg} workspace The main workspace. - * @returns {boolean} True if the cursor and marker locations were connected, - * false otherwise. - * @package - */ - connectMarkerAndCursor(workspace) { - const markerNode = this.getMarker(workspace).getCurNode(); - const cursorNode = workspace.getCursor().getCurNode(); - - if (markerNode && cursorNode) { - return this.tryToConnectMarkerAndCursor( - workspace, - markerNode, - cursorNode, - ); - } - return false; - } - - /** - * Tries to connect the given marker and cursor node. - * @param {!Blockly.WorkspaceSvg} workspace The main workspace. - * @param {!Blockly.ASTNode} markerNode The node to try to connect to. - * @param {!Blockly.ASTNode} cursorNode The node to connect to the markerNode. - * @returns {boolean} True if the key was handled; false if something went - * wrong. - * @protected - */ - tryToConnectMarkerAndCursor(workspace, markerNode, cursorNode) { - if (!this.logConnectionWarning(markerNode, cursorNode)) { - return false; - } - - const markerType = markerNode.getType(); - const cursorType = cursorNode.getType(); - - const cursorLoc = cursorNode.getLocation(); - const markerLoc = markerNode.getLocation(); - if (markerNode.isConnection() && cursorNode.isConnection()) { - const cursorConnection = /** @type {!Blockly.RenderedConnection} */ ( - cursorLoc - ); - const markerConnection = /** @type {!Blockly.RenderedConnection} */ ( - markerLoc - ); - return this.connect(cursorConnection, markerConnection); - } else if ( - markerNode.isConnection() && - (cursorType == Blockly.ASTNode.types.BLOCK || - cursorType == Blockly.ASTNode.types.STACK) - ) { - const cursorBlock = /** @type {!Blockly.BlockSvg} */ (cursorLoc); - const markerConnection = /** @type {!Blockly.RenderedConnection} */ ( - markerLoc - ); - return this.insertBlock(cursorBlock, markerConnection); - } else if (markerType == Blockly.ASTNode.types.WORKSPACE) { - const block = cursorNode ? cursorNode.getSourceBlock() : null; - return this.moveBlockToWorkspace( - /** @type {Blockly.BlockSvg} */ (block), - markerNode, - ); - } - this.warn('Unexpected state in tryToConnectMarkerAndCursor.'); - return false; - } - - /** - * Warns the user if the given cursor or marker node can not be connected. - * @param {!Blockly.ASTNode} markerNode The node to try to connect to. - * @param {!Blockly.ASTNode} cursorNode The node to connect to the markerNode. - * @returns {boolean} True if the marker and cursor are valid types, false - * otherwise. - * @protected - */ - logConnectionWarning(markerNode, cursorNode) { - if (!markerNode) { - this.warn('Cannot insert with no marked node.'); - return false; - } - - if (!cursorNode) { - this.warn('Cannot insert with no cursor node.'); - return false; - } - const markerType = markerNode.getType(); - const cursorType = cursorNode.getType(); - - // Check the marker for invalid types. - if (markerType == Blockly.ASTNode.types.FIELD) { - this.warn('Should not have been able to mark a field.'); - return false; - } else if (markerType == Blockly.ASTNode.types.BLOCK) { - this.warn('Should not have been able to mark a block.'); - return false; - } else if (markerType == Blockly.ASTNode.types.STACK) { - this.warn('Should not have been able to mark a stack.'); - return false; - } - - // Check the cursor for invalid types. - if (cursorType == Blockly.ASTNode.types.FIELD) { - this.warn('Cannot attach a field to anything else.'); - return false; - } else if (cursorType == Blockly.ASTNode.types.WORKSPACE) { - this.warn('Cannot attach a workspace to anything else.'); - return false; - } - return true; - } - - /** - * Disconnects the block from its parent and moves it to the position of the - * workspace node. - * @param {?Blockly.BlockSvg} block The block to be moved to the workspace. - * @param {!Blockly.ASTNode} wsNode The workspace node holding the position - * the block will be moved to. - * @returns {boolean} True if the block can be moved to the workspace, - * false otherwise. - * @protected - */ - moveBlockToWorkspace(block, wsNode) { - if (!block) { - return false; - } - if (block.isShadow()) { - this.warn('Cannot move a shadow block to the workspace.'); - return false; - } - if (block.getParent()) { - block.unplug(false); - } - block.moveTo(wsNode.getWsCoordinate()); - return true; - } - - /** - * Disconnects the child block from its parent block. No-op if the two given - * connections are unrelated. - * @param {!Blockly.RenderedConnection} movingConnection The connection that - * is being moved. - * @param {!Blockly.RenderedConnection} destConnection The connection to be - * moved to. - * @protected - */ - disconnectChild(movingConnection, destConnection) { - const movingBlock = movingConnection.getSourceBlock(); - const destBlock = destConnection.getSourceBlock(); - let inferiorConnection; - - if (movingBlock.getRootBlock() === destBlock.getRootBlock()) { - if (movingBlock.getDescendants(false).indexOf(destBlock) > -1) { - inferiorConnection = this.getInferiorConnection(destConnection); - if (inferiorConnection) { - inferiorConnection.disconnect(); - } - } else { - inferiorConnection = this.getInferiorConnection(movingConnection); - if (inferiorConnection) { - inferiorConnection.disconnect(); - } - } - } - } - - /** - * Tries to connect the given connections. - * - * If the given connections are not compatible try finding compatible - * connections on the source blocks of the given connections. - * @param {?Blockly.RenderedConnection} movingConnection The connection that - * is being moved. - * @param {?Blockly.RenderedConnection} destConnection The connection to be - * moved to. - * @returns {boolean} True if the two connections or their target connections - * were connected, false otherwise. - * @protected - */ - connect(movingConnection, destConnection) { - if (!movingConnection || !destConnection) { - return false; - } - - const movingInferior = this.getInferiorConnection(movingConnection); - const destSuperior = this.getSuperiorConnection(destConnection); - - const movingSuperior = this.getSuperiorConnection(movingConnection); - const destInferior = this.getInferiorConnection(destConnection); - - if ( - movingInferior && - destSuperior && - this.moveAndConnect(movingInferior, destSuperior) - ) { - return true; - // Try swapping the inferior and superior connections on the blocks. - } else if ( - movingSuperior && - destInferior && - this.moveAndConnect(movingSuperior, destInferior) - ) { - return true; - } else if (this.moveAndConnect(movingConnection, destConnection)) { - return true; - } else { - const checker = movingConnection.getConnectionChecker(); - const reason = checker.canConnectWithReason( - movingConnection, - destConnection, - false, - ); - this.warn( - 'Connection failed with error: ' + - checker.getErrorMessage(reason, movingConnection, destConnection), - ); - return false; - } - } - - /** - * Finds the inferior connection on the source block if the given connection - * is superior. - * @param {?Blockly.RenderedConnection} connection The connection trying to be - * connected. - * @returns {?Blockly.RenderedConnection} The inferior connection or null if - * none exists. - * @protected - */ - getInferiorConnection(connection) { - const block = /** @type{!Blockly.BlockSvg} */ (connection.getSourceBlock()); - if (!connection.isSuperior()) { - return connection; - } else if (block.previousConnection) { - return block.previousConnection; - } else if (block.outputConnection) { - return block.outputConnection; - } else { - return null; - } - } - - /** - * Finds a superior connection on the source block if the given connection is - * inferior. - * @param {?Blockly.RenderedConnection} connection The connection trying to be - * connected. - * @returns {?Blockly.RenderedConnection} The superior connection or null if - * none exists. - * @protected - */ - getSuperiorConnection(connection) { - if (connection.isSuperior()) { - return connection; - } else if (connection.targetConnection) { - return connection.targetConnection; - } - return null; - } - - /** - * Moves the moving connection to the target connection and connects them. - * @param {?Blockly.RenderedConnection} movingConnection The connection that - * is being moved. - * @param {?Blockly.RenderedConnection} destConnection The connection to be - * moved to. - * @returns {boolean} True if the connections were connected, false otherwise. - * @protected - */ - moveAndConnect(movingConnection, destConnection) { - if (!movingConnection || !destConnection) { - return false; - } - const movingBlock = movingConnection.getSourceBlock(); - const checker = movingConnection.getConnectionChecker(); - - if ( - checker.canConnect(movingConnection, destConnection, false) && - !destConnection.getSourceBlock().isShadow() - ) { - this.disconnectChild(movingConnection, destConnection); - - // Position the root block near the connection so it does not move the - // other block when they are connected. - if (!destConnection.isSuperior()) { - const rootBlock = movingBlock.getRootBlock(); - - const originalOffsetToTarget = { - x: destConnection.x - movingConnection.x, - y: destConnection.y - movingConnection.y, - }; - const originalOffsetInBlock = movingConnection - .getOffsetInBlock() - .clone(); - rootBlock.positionNearConnection( - movingConnection, - originalOffsetToTarget, - originalOffsetInBlock, - ); - } - destConnection.connect(movingConnection); - return true; - } - return false; - } - - /** - * Tries to connect the given block to the destination connection, making an - * intelligent guess about which connection to use on the moving block. - * @param {!Blockly.BlockSvg} block The block to move. - * @param {!Blockly.RenderedConnection} destConnection The connection to - * connect to. - * @returns {boolean} Whether the connection was successful. - * @protected - */ - insertBlock(block, destConnection) { - switch (destConnection.type) { - case Blockly.PREVIOUS_STATEMENT: - if (this.connect(block.nextConnection, destConnection)) { - return true; - } - break; - case Blockly.NEXT_STATEMENT: - if (this.connect(block.previousConnection, destConnection)) { - return true; - } - break; - case Blockly.INPUT_VALUE: - if (this.connect(block.outputConnection, destConnection)) { - return true; - } - break; - case Blockly.OUTPUT_VALUE: - for (let i = 0; i < block.inputList.length; i++) { - const inputConnection = /** @type {Blockly.RenderedConnection} */ ( - block.inputList[i].connection - ); - if ( - inputConnection && - inputConnection.type === Blockly.INPUT_VALUE && - this.connect(inputConnection, destConnection) - ) { - return true; - } - } - // If there are no input values pass the output and destination - // connections to connect_ to find a way to connect the two. - if ( - block.outputConnection && - this.connect(block.outputConnection, destConnection) - ) { - return true; - } - break; - } - this.warn('This block can not be inserted at the marked location.'); - return false; - } - - /** - * Disconnects the connection that the cursor is pointing to, and bump blocks. - * This is a no-op if the connection cannot be broken or if the cursor is not - * pointing to a connection. - * @param {!Blockly.WorkspaceSvg} workspace The workspace. - * @package - */ - disconnectBlocks(workspace) { - const curNode = workspace.getCursor().getCurNode(); - if (!curNode.isConnection()) { - this.log( - 'Cannot disconnect blocks when the cursor is not on a connection', - ); - return; - } - const curConnection = /** @type {!Blockly.RenderedConnection} */ ( - curNode.getLocation() - ); - if (!curConnection.isConnected()) { - this.log('Cannot disconnect unconnected connection'); - return; - } - const superiorConnection = curConnection.isSuperior() - ? curConnection - : curConnection.targetConnection; - - const inferiorConnection = curConnection.isSuperior() - ? curConnection.targetConnection - : curConnection; - - if (inferiorConnection.getSourceBlock().isShadow()) { - this.log('Cannot disconnect a shadow block'); - return; - } - superiorConnection.disconnect(); - inferiorConnection.bumpAwayFrom(superiorConnection); - - const rootBlock = superiorConnection.getSourceBlock().getRootBlock(); - rootBlock.bringToFront(); - - const connectionNode = - Blockly.ASTNode.createConnectionNode(superiorConnection); - workspace.getCursor().setCurNode(connectionNode); - } - - /** - * Moves the marker to the cursor's current location. - * @param {!Blockly.WorkspaceSvg} workspace The workspace. - * @protected - */ - markAtCursor(workspace) { - this.getMarker(workspace).setCurNode(workspace.getCursor().getCurNode()); - } - - /** - * Removes the marker from its current location and hide it. - * @param {!Blockly.WorkspaceSvg} workspace The workspace. - * @protected - */ - removeMark(workspace) { - const marker = this.getMarker(workspace); - marker.setCurNode(null); - marker.hide(); - } - - /** - * Enables accessibility mode. - * @param {!Blockly.WorkspaceSvg} workspace The workspace to enable keyboard - * accessibility mode on. - * @package - */ - enableKeyboardAccessibility(workspace) { - if ( - this.workspaces.indexOf(workspace) > -1 && - !workspace.keyboardAccessibilityMode - ) { - workspace.keyboardAccessibilityMode = true; - this.focusWorkspace(workspace); - } - } - - /** - * Disables accessibility mode. - * @param {!Blockly.WorkspaceSvg} workspace The workspace to disable keyboard - * accessibility mode on. - * @package - */ - disableKeyboardAccessibility(workspace) { - if ( - this.workspaces.indexOf(workspace) > -1 && - workspace.keyboardAccessibilityMode - ) { - workspace.keyboardAccessibilityMode = false; - workspace.getCursor().hide(); - this.getMarker(workspace).hide(); - if (this.getFlyoutCursor(workspace)) { - this.getFlyoutCursor(workspace).hide(); - } - } - } - - /** - * Navigation log handler. If loggingCallback is defined, use it. - * Otherwise just log to the console.log. - * @param {string} msg The message to log. - * @protected - */ - log(msg) { - if (this.loggingCallback) { - this.loggingCallback(Constants.LOGGING_MSG_TYPE.LOG, msg); - } else { - console.log(msg); - } - } - - /** - * Navigation warning handler. If loggingCallback is defined, use it. - * Otherwise call console.warn. - * @param {string} msg The warning message. - * @protected - */ - warn(msg) { - if (this.loggingCallback) { - this.loggingCallback(Constants.LOGGING_MSG_TYPE.WARN, msg); - } else { - console.warn(msg); - } - } - - /** - * Navigation error handler. If loggingCallback is defined, use it. - * Otherwise call console.error. - * @param {string} msg The error message. - * @protected - */ - error(msg) { - if (this.loggingCallback) { - this.loggingCallback(Constants.LOGGING_MSG_TYPE.ERROR, msg); - } else { - console.error(msg); - } - } - - /** - * Moves the workspace cursor in the given direction. - * @param {!Blockly.WorkspaceSvg} workspace The workspace the cursor is on. - * @param {number} xDirection -1 to move cursor left. 1 to move cursor right. - * @param {number} yDirection -1 to move cursor up. 1 to move cursor down. - * @returns {boolean} True if the current node is a workspace, false - * otherwise. - * @package - */ - moveWSCursor(workspace, xDirection, yDirection) { - const cursor = workspace.getCursor(); - const curNode = workspace.getCursor().getCurNode(); - - if (curNode.getType() !== Blockly.ASTNode.types.WORKSPACE) { - return false; - } - - const wsCoord = curNode.getWsCoordinate(); - const newX = xDirection * this.WS_MOVE_DISTANCE + wsCoord.x; - const newY = yDirection * this.WS_MOVE_DISTANCE + wsCoord.y; - - cursor.setCurNode( - Blockly.ASTNode.createWorkspaceNode( - workspace, - new Blockly.utils.Coordinate(newX, newY), - ), - ); - return true; - } - - /** - * Handles hitting the enter key on the workspace. - * @param {!Blockly.WorkspaceSvg} workspace The workspace. - * @package - */ - handleEnterForWS(workspace) { - const cursor = workspace.getCursor(); - const curNode = cursor.getCurNode(); - const nodeType = curNode.getType(); - if (nodeType == Blockly.ASTNode.types.FIELD) { - /** @type {!Blockly.Field} */ (curNode.getLocation()).showEditor(); - } else if ( - curNode.isConnection() || - nodeType == Blockly.ASTNode.types.WORKSPACE - ) { - this.markAtCursor(workspace); - } else if (nodeType == Blockly.ASTNode.types.BLOCK) { - this.warn('Cannot mark a block.'); - } else if (nodeType == Blockly.ASTNode.types.STACK) { - this.warn('Cannot mark a stack.'); - } - } - - /** - * Pastes the copied block to the marked location. - * @param {Blockly.BlockCopyData} copyData The data - * to paste into the workspace. - * @param {Blockly.WorkspaceSvg} workspace The workspace to paste the data - * into. - * @returns {boolean} True if the paste was sucessful, false otherwise. - * @package - */ - paste(copyData, workspace) { - let isHandled = false; - Blockly.Events.setGroup(true); - const block = /** @type {Blockly.BlockSvg} */ ( - Blockly.clipboard.paste(copyData, workspace) - ); - if (block) { - isHandled = this.insertPastedBlock(workspace, block); - } - Blockly.Events.setGroup(false); - return isHandled; - } - - /** - * Inserts the pasted block at the marked location if a compatible connection - * exists. If no connection has been marked, or there is not a compatible - * connection then the block is placed on the workspace. - * @param {!Blockly.WorkspaceSvg} workspace The workspace to paste the block - * on. - * @param {!Blockly.BlockSvg} block The block to paste. - * @returns {boolean} True if the block was pasted to the workspace, false - * otherwise. - * @protected - */ - insertPastedBlock(workspace, block) { - let isHandled = false; - const markedNode = workspace.getMarker(this.MARKER_NAME).getCurNode(); - if (markedNode) { - isHandled = this.tryToConnectMarkerAndCursor( - workspace, - markedNode, - Blockly.ASTNode.createBlockNode(block), - ); - } - return isHandled; - } - - /** - * Triggers a flyout button's callback. - * @param {!Blockly.WorkspaceSvg} workspace The main workspace. The workspace - * containing a flyout with a button. - * @package - */ - triggerButtonCallback(workspace) { - const button = /** @type {!Blockly.FlyoutButton} */ ( - this.getFlyoutCursor(workspace).getCurNode().getLocation() - ); - const buttonCallback = workspace.flyoutButtonCallbacks.get( - button.callbackKey, - ); - if (typeof buttonCallback === 'function') { - buttonCallback(button); - } else { - throw new Error('No callback function found for flyout button.'); - } - } - - /** - * Removes the change listeners on all registered workspaces. - * @package - */ - dispose() { - for (const workspace of this.workspaces) { - this.removeWorkspace(workspace); - } - } -} diff --git a/plugins/keyboard-navigation/src/navigation_controller.js b/plugins/keyboard-navigation/src/navigation_controller.js deleted file mode 100644 index d3eaa4c1c..000000000 --- a/plugins/keyboard-navigation/src/navigation_controller.js +++ /dev/null @@ -1,1020 +0,0 @@ -/** - * @license - * Copyright 2021 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -/** - * @fileoverview Registers all of the keyboard shortcuts that are necessary for - * navigating blockly using the keyboard. - * @author aschmiedt@google.com (Abby Schmiedt) - */ - -import './gesture_monkey_patch'; - -import * as Blockly from 'blockly/core'; - -import * as Constants from './constants'; -import {Navigation} from './navigation'; - -/** - * Class for registering shortcuts for keyboard navigation. - */ -export class NavigationController { - /** Data copied by the copy or cut keyboard shortcuts. */ - copyData = null; - - /** The workspace a copy or cut keyboard shortcut happened in. */ - copyWorkspace = null; - - /** - * Constructor used for registering shortcuts. - * This will register any default shortcuts for keyboard navigation. - * This is intended to be a singleton. - * @param {!Navigation=} optNavigation The class that handles keyboard - * navigation shortcuts. (Ex: inserting a block, focusing the flyout). - */ - constructor(optNavigation) { - /** - * Handles any keyboard navigation shortcuts. - * @type {!Navigation} - * @public - */ - this.navigation = optNavigation || new Navigation(); - } - - /** - * Registers the default keyboard shortcuts for keyboard navigation. - * @public - */ - init() { - this.addShortcutHandlers(); - this.registerDefaults(); - } - - /** - * Adds methods to core Blockly components that allows them to handle keyboard - * shortcuts when in keyboard navigation mode. - * @protected - */ - addShortcutHandlers() { - if (Blockly.FieldDropdown) { - Blockly.FieldDropdown.prototype.onShortcut = this.fieldDropdownHandler; - } - - if (Blockly.Toolbox) { - Blockly.Toolbox.prototype.onShortcut = this.toolboxHandler; - } - } - - /** - * Removes methods on core Blockly components that allows them to handle - * keyboard shortcuts. - * @protected - */ - removeShortcutHandlers() { - if (Blockly.FieldDropdown) { - Blockly.FieldDropdown.prototype.onShortcut = null; - } - - if (Blockly.Toolbox) { - Blockly.Toolbox.prototype.onShortcut = null; - } - } - - /** - * Handles the given keyboard shortcut. - * This is only triggered when keyboard accessibility mode is enabled. - * @param {!Blockly.ShortcutRegistry.KeyboardShortcut} shortcut The shortcut - * to be handled. - * @returns {boolean} True if the field handled the shortcut, - * false otherwise. - * @this {Blockly.FieldDropdown} - * @protected - */ - fieldDropdownHandler(shortcut) { - if (this.menu_) { - switch (shortcut.name) { - case Constants.SHORTCUT_NAMES.PREVIOUS: - this.menu_.highlightPrevious(); - return true; - case Constants.SHORTCUT_NAMES.NEXT: - this.menu_.highlightNext(); - return true; - default: - return false; - } - } - // If we haven't already handled the shortcut, let the default Field - // handler try. - return Blockly.Field.prototype.onShortcut.call(this, shortcut); - } - - /** - * Handles the given keyboard shortcut. - * This is only triggered when keyboard accessibility mode is enabled. - * @param {!Blockly.ShortcutRegistry.KeyboardShortcut} shortcut The shortcut - * to be handled. - * @returns {boolean} True if the toolbox handled the shortcut, - * false otherwise. - * @this {Blockly.Toolbox} - * @protected - */ - toolboxHandler(shortcut) { - if (!this.selectedItem_) { - return false; - } - switch (shortcut.name) { - case Constants.SHORTCUT_NAMES.PREVIOUS: - return this.selectPrevious(); - case Constants.SHORTCUT_NAMES.OUT: - return this.selectParent(); - case Constants.SHORTCUT_NAMES.NEXT: - return this.selectNext(); - case Constants.SHORTCUT_NAMES.IN: - return this.selectChild(); - default: - return false; - } - } - - /** - * Adds all necessary event listeners and markers to a workspace for keyboard - * navigation to work. This must be called for keyboard navigation to work - * on a workspace. - * @param {!Blockly.WorkspaceSvg} workspace The workspace to add keyboard - * navigation to. - * @public - */ - addWorkspace(workspace) { - this.navigation.addWorkspace(workspace); - } - - /** - * Removes all necessary event listeners and markers to a workspace for - * keyboard navigation to work. - * @param {!Blockly.WorkspaceSvg} workspace The workspace to remove keyboard - * navigation from. - * @public - */ - removeWorkspace(workspace) { - this.navigation.removeWorkspace(workspace); - } - - /** - * Turns on keyboard navigation. - * @param {!Blockly.WorkspaceSvg} workspace The workspace to turn on keyboard - * navigation for. - * @public - */ - enable(workspace) { - this.navigation.enableKeyboardAccessibility(workspace); - } - - /** - * Turns off keyboard navigation. - * @param {!Blockly.WorkspaceSvg} workspace The workspace to turn off keyboard - * navigation on. - * @public - */ - disable(workspace) { - this.navigation.disableKeyboardAccessibility(workspace); - } - - /** - * Gives the cursor to the field to handle if the cursor is on a field. - * @param {!Blockly.WorkspaceSvg} workspace The workspace to check. - * @param {!Blockly.ShortcutRegistry.KeyboardShortcut} shortcut The shortcut - * to give to the field. - * @returns {boolean} True if the shortcut was handled by the field, false - * otherwise. - * @protected - */ - fieldShortcutHandler(workspace, shortcut) { - const cursor = workspace.getCursor(); - if (!cursor || !cursor.getCurNode()) { - return false; - } - const curNode = cursor.getCurNode(); - if (curNode.getType() === Blockly.ASTNode.types.FIELD) { - return /** @type {!Blockly.Field} */ (curNode.getLocation()).onShortcut( - shortcut, - ); - } - return false; - } - - /** - * Keyboard shortcut to go to the previous location when in keyboard - * navigation mode. - * @protected - */ - registerPrevious() { - /** @type {!Blockly.ShortcutRegistry.KeyboardShortcut} */ - const previousShortcut = { - name: Constants.SHORTCUT_NAMES.PREVIOUS, - preconditionFn: (workspace) => { - return workspace.keyboardAccessibilityMode; - }, - callback: (workspace, e, shortcut) => { - const flyout = workspace.getFlyout(); - const toolbox = workspace.getToolbox(); - let isHandled = false; - switch (this.navigation.getState(workspace)) { - case Constants.STATE.WORKSPACE: - isHandled = this.fieldShortcutHandler(workspace, shortcut); - if (!isHandled) { - workspace.getCursor().prev(); - isHandled = true; - } - return isHandled; - case Constants.STATE.FLYOUT: - isHandled = this.fieldShortcutHandler(workspace, shortcut); - if (!isHandled) { - flyout.getWorkspace().getCursor().prev(); - isHandled = true; - } - return isHandled; - case Constants.STATE.TOOLBOX: - return toolbox && typeof toolbox.onShortcut == 'function' - ? toolbox.onShortcut(shortcut) - : false; - default: - return false; - } - }, - }; - - Blockly.ShortcutRegistry.registry.register(previousShortcut); - Blockly.ShortcutRegistry.registry.addKeyMapping( - Blockly.utils.KeyCodes.W, - previousShortcut.name, - ); - } - - /** - * Keyboard shortcut to turn keyboard navigation on or off. - * @protected - */ - registerToggleKeyboardNav() { - /** @type {!Blockly.ShortcutRegistry.KeyboardShortcut} */ - const toggleKeyboardNavShortcut = { - name: Constants.SHORTCUT_NAMES.TOGGLE_KEYBOARD_NAV, - callback: (workspace) => { - if (workspace.keyboardAccessibilityMode) { - this.navigation.disableKeyboardAccessibility(workspace); - } else { - this.navigation.enableKeyboardAccessibility(workspace); - } - return true; - }, - }; - - Blockly.ShortcutRegistry.registry.register(toggleKeyboardNavShortcut); - const ctrlShiftK = Blockly.ShortcutRegistry.registry.createSerializedKey( - Blockly.utils.KeyCodes.K, - [Blockly.utils.KeyCodes.CTRL, Blockly.utils.KeyCodes.SHIFT], - ); - Blockly.ShortcutRegistry.registry.addKeyMapping( - ctrlShiftK, - toggleKeyboardNavShortcut.name, - ); - } - - /** - * Keyboard shortcut to go to the out location when in keyboard navigation - * mode. - * @protected - */ - registerOut() { - /** @type {!Blockly.ShortcutRegistry.KeyboardShortcut} */ - const outShortcut = { - name: Constants.SHORTCUT_NAMES.OUT, - preconditionFn: (workspace) => { - return workspace.keyboardAccessibilityMode; - }, - callback: (workspace, e, shortcut) => { - const toolbox = workspace.getToolbox(); - let isHandled = false; - switch (this.navigation.getState(workspace)) { - case Constants.STATE.WORKSPACE: - isHandled = this.fieldShortcutHandler(workspace, shortcut); - if (!isHandled) { - workspace.getCursor().out(); - isHandled = true; - } - return isHandled; - case Constants.STATE.FLYOUT: - this.navigation.focusToolbox(workspace); - return true; - case Constants.STATE.TOOLBOX: - return toolbox && typeof toolbox.onShortcut == 'function' - ? toolbox.onShortcut(shortcut) - : false; - default: - return false; - } - }, - }; - - Blockly.ShortcutRegistry.registry.register(outShortcut); - Blockly.ShortcutRegistry.registry.addKeyMapping( - Blockly.utils.KeyCodes.A, - outShortcut.name, - ); - } - - /** - * Keyboard shortcut to go to the next location when in keyboard navigation - * mode. - * @protected - */ - registerNext() { - /** @type {!Blockly.ShortcutRegistry.KeyboardShortcut} */ - const nextShortcut = { - name: Constants.SHORTCUT_NAMES.NEXT, - preconditionFn: (workspace) => { - return workspace.keyboardAccessibilityMode; - }, - callback: (workspace, e, shortcut) => { - const toolbox = workspace.getToolbox(); - const flyout = workspace.getFlyout(); - let isHandled = false; - switch (this.navigation.getState(workspace)) { - case Constants.STATE.WORKSPACE: - isHandled = this.fieldShortcutHandler(workspace, shortcut); - if (!isHandled) { - workspace.getCursor().next(); - isHandled = true; - } - return isHandled; - case Constants.STATE.FLYOUT: - isHandled = this.fieldShortcutHandler(workspace, shortcut); - if (!isHandled) { - flyout.getWorkspace().getCursor().next(); - isHandled = true; - } - return isHandled; - case Constants.STATE.TOOLBOX: - return toolbox && typeof toolbox.onShortcut == 'function' - ? toolbox.onShortcut(shortcut) - : false; - default: - return false; - } - }, - }; - - Blockly.ShortcutRegistry.registry.register(nextShortcut); - Blockly.ShortcutRegistry.registry.addKeyMapping( - Blockly.utils.KeyCodes.S, - nextShortcut.name, - ); - } - - /** - * Keyboard shortcut to go to the in location when in keyboard navigation - * mode. - * @protected - */ - registerIn() { - /** @type {!Blockly.ShortcutRegistry.KeyboardShortcut} */ - const inShortcut = { - name: Constants.SHORTCUT_NAMES.IN, - preconditionFn: (workspace) => { - return workspace.keyboardAccessibilityMode; - }, - callback: (workspace, e, shortcut) => { - const toolbox = workspace.getToolbox(); - let isHandled = false; - switch (this.navigation.getState(workspace)) { - case Constants.STATE.WORKSPACE: - isHandled = this.fieldShortcutHandler(workspace, shortcut); - if (!isHandled) { - workspace.getCursor().in(); - isHandled = true; - } - return isHandled; - case Constants.STATE.TOOLBOX: - isHandled = - toolbox && typeof toolbox.onShortcut == 'function' - ? toolbox.onShortcut(shortcut) - : false; - if (!isHandled) { - this.navigation.focusFlyout(workspace); - } - return true; - default: - return false; - } - }, - }; - - Blockly.ShortcutRegistry.registry.register(inShortcut); - Blockly.ShortcutRegistry.registry.addKeyMapping( - Blockly.utils.KeyCodes.D, - inShortcut.name, - ); - } - - /** - * Keyboard shortcut to connect a block to a marked location when in keyboard - * navigation mode. - * @protected - */ - registerInsert() { - /** @type {!Blockly.ShortcutRegistry.KeyboardShortcut} */ - const insertShortcut = { - name: Constants.SHORTCUT_NAMES.INSERT, - preconditionFn: (workspace) => { - return ( - workspace.keyboardAccessibilityMode && !workspace.options.readOnly - ); - }, - callback: (workspace) => { - switch (this.navigation.getState(workspace)) { - case Constants.STATE.WORKSPACE: - return this.navigation.connectMarkerAndCursor(workspace); - default: - return false; - } - }, - }; - - Blockly.ShortcutRegistry.registry.register(insertShortcut); - Blockly.ShortcutRegistry.registry.addKeyMapping( - Blockly.utils.KeyCodes.I, - insertShortcut.name, - ); - } - - /** - * Keyboard shortcut to mark a location when in keyboard navigation mode. - * @protected - */ - registerMark() { - /** @type {!Blockly.ShortcutRegistry.KeyboardShortcut} */ - const markShortcut = { - name: Constants.SHORTCUT_NAMES.MARK, - preconditionFn: (workspace) => { - return ( - workspace.keyboardAccessibilityMode && !workspace.options.readOnly - ); - }, - callback: (workspace) => { - let flyoutCursor; - let curNode; - let nodeType; - - switch (this.navigation.getState(workspace)) { - case Constants.STATE.WORKSPACE: - this.navigation.handleEnterForWS(workspace); - return true; - case Constants.STATE.FLYOUT: - flyoutCursor = this.navigation.getFlyoutCursor(workspace); - if (!flyoutCursor) { - return false; - } - curNode = flyoutCursor.getCurNode(); - nodeType = curNode.getType(); - - switch (nodeType) { - case Blockly.ASTNode.types.STACK: - this.navigation.insertFromFlyout(workspace); - break; - case Blockly.ASTNode.types.BUTTON: - this.navigation.triggerButtonCallback(workspace); - break; - } - - return true; - default: - return false; - } - }, - }; - - Blockly.ShortcutRegistry.registry.register(markShortcut); - Blockly.ShortcutRegistry.registry.addKeyMapping( - Blockly.utils.KeyCodes.ENTER, - markShortcut.name, - ); - } - - /** - * Keyboard shortcut to disconnect two blocks when in keyboard navigation - * mode. - * @protected - */ - registerDisconnect() { - /** @type {!Blockly.ShortcutRegistry.KeyboardShortcut} */ - const disconnectShortcut = { - name: Constants.SHORTCUT_NAMES.DISCONNECT, - preconditionFn: (workspace) => { - return ( - workspace.keyboardAccessibilityMode && !workspace.options.readOnly - ); - }, - callback: (workspace) => { - switch (this.navigation.getState(workspace)) { - case Constants.STATE.WORKSPACE: - this.navigation.disconnectBlocks(workspace); - return true; - default: - return false; - } - }, - }; - - Blockly.ShortcutRegistry.registry.register(disconnectShortcut); - Blockly.ShortcutRegistry.registry.addKeyMapping( - Blockly.utils.KeyCodes.X, - disconnectShortcut.name, - ); - } - - /** - * Keyboard shortcut to focus on the toolbox when in keyboard navigation - * mode. - * @protected - */ - registerToolboxFocus() { - /** @type {!Blockly.ShortcutRegistry.KeyboardShortcut} */ - const focusToolboxShortcut = { - name: Constants.SHORTCUT_NAMES.TOOLBOX, - preconditionFn: (workspace) => { - return ( - workspace.keyboardAccessibilityMode && !workspace.options.readOnly - ); - }, - callback: (workspace) => { - switch (this.navigation.getState(workspace)) { - case Constants.STATE.WORKSPACE: - if (!workspace.getToolbox()) { - this.navigation.focusFlyout(workspace); - } else { - this.navigation.focusToolbox(workspace); - } - return true; - default: - return false; - } - }, - }; - - Blockly.ShortcutRegistry.registry.register(focusToolboxShortcut); - Blockly.ShortcutRegistry.registry.addKeyMapping( - Blockly.utils.KeyCodes.T, - focusToolboxShortcut.name, - ); - } - - /** - * Keyboard shortcut to exit the current location and focus on the workspace - * when in keyboard navigation mode. - * @protected - */ - registerExit() { - /** @type {!Blockly.ShortcutRegistry.KeyboardShortcut} */ - const exitShortcut = { - name: Constants.SHORTCUT_NAMES.EXIT, - preconditionFn: (workspace) => { - return workspace.keyboardAccessibilityMode; - }, - callback: (workspace) => { - switch (this.navigation.getState(workspace)) { - case Constants.STATE.FLYOUT: - this.navigation.focusWorkspace(workspace); - return true; - case Constants.STATE.TOOLBOX: - this.navigation.focusWorkspace(workspace); - return true; - default: - return false; - } - }, - }; - - Blockly.ShortcutRegistry.registry.register(exitShortcut, true); - Blockly.ShortcutRegistry.registry.addKeyMapping( - Blockly.utils.KeyCodes.ESC, - exitShortcut.name, - true, - ); - Blockly.ShortcutRegistry.registry.addKeyMapping( - Blockly.utils.KeyCodes.E, - exitShortcut.name, - true, - ); - } - - /** - * Keyboard shortcut to move the cursor on the workspace to the left when in - * keyboard navigation mode. - * @protected - */ - registerWorkspaceMoveLeft() { - /** @type {!Blockly.ShortcutRegistry.KeyboardShortcut} */ - const wsMoveLeftShortcut = { - name: Constants.SHORTCUT_NAMES.MOVE_WS_CURSOR_LEFT, - preconditionFn: (workspace) => { - return ( - workspace.keyboardAccessibilityMode && !workspace.options.readOnly - ); - }, - callback: (workspace) => { - return this.navigation.moveWSCursor(workspace, -1, 0); - }, - }; - - Blockly.ShortcutRegistry.registry.register(wsMoveLeftShortcut); - const shiftA = Blockly.ShortcutRegistry.registry.createSerializedKey( - Blockly.utils.KeyCodes.A, - [Blockly.utils.KeyCodes.SHIFT], - ); - Blockly.ShortcutRegistry.registry.addKeyMapping( - shiftA, - wsMoveLeftShortcut.name, - ); - } - - /** - * Keyboard shortcut to move the cursor on the workspace to the right when in - * keyboard navigation mode. - * @protected - */ - registerWorkspaceMoveRight() { - /** @type {!Blockly.ShortcutRegistry.KeyboardShortcut} */ - const wsMoveRightShortcut = { - name: Constants.SHORTCUT_NAMES.MOVE_WS_CURSOR_RIGHT, - preconditionFn: (workspace) => { - return ( - workspace.keyboardAccessibilityMode && !workspace.options.readOnly - ); - }, - callback: (workspace) => { - return this.navigation.moveWSCursor(workspace, 1, 0); - }, - }; - - Blockly.ShortcutRegistry.registry.register(wsMoveRightShortcut); - const shiftD = Blockly.ShortcutRegistry.registry.createSerializedKey( - Blockly.utils.KeyCodes.D, - [Blockly.utils.KeyCodes.SHIFT], - ); - Blockly.ShortcutRegistry.registry.addKeyMapping( - shiftD, - wsMoveRightShortcut.name, - ); - } - - /** - * Keyboard shortcut to move the cursor on the workspace up when in keyboard - * navigation mode. - * @protected - */ - registerWorkspaceMoveUp() { - /** @type {!Blockly.ShortcutRegistry.KeyboardShortcut} */ - const wsMoveUpShortcut = { - name: Constants.SHORTCUT_NAMES.MOVE_WS_CURSOR_UP, - preconditionFn: (workspace) => { - return ( - workspace.keyboardAccessibilityMode && !workspace.options.readOnly - ); - }, - callback: (workspace) => { - return this.navigation.moveWSCursor(workspace, 0, -1); - }, - }; - - Blockly.ShortcutRegistry.registry.register(wsMoveUpShortcut); - const shiftW = Blockly.ShortcutRegistry.registry.createSerializedKey( - Blockly.utils.KeyCodes.W, - [Blockly.utils.KeyCodes.SHIFT], - ); - Blockly.ShortcutRegistry.registry.addKeyMapping( - shiftW, - wsMoveUpShortcut.name, - ); - } - - /** - * Keyboard shortcut to move the cursor on the workspace down when in - * keyboard navigation mode. - * @protected - */ - registerWorkspaceMoveDown() { - /** @type {!Blockly.ShortcutRegistry.KeyboardShortcut} */ - const wsMoveDownShortcut = { - name: Constants.SHORTCUT_NAMES.MOVE_WS_CURSOR_DOWN, - preconditionFn: (workspace) => { - return ( - workspace.keyboardAccessibilityMode && !workspace.options.readOnly - ); - }, - callback: (workspace) => { - return this.navigation.moveWSCursor(workspace, 0, 1); - }, - }; - - Blockly.ShortcutRegistry.registry.register(wsMoveDownShortcut); - const shiftW = Blockly.ShortcutRegistry.registry.createSerializedKey( - Blockly.utils.KeyCodes.S, - [Blockly.utils.KeyCodes.SHIFT], - ); - Blockly.ShortcutRegistry.registry.addKeyMapping( - shiftW, - wsMoveDownShortcut.name, - ); - } - - /** - * Keyboard shortcut to copy the block the cursor is currently on. - * @protected - */ - registerCopy() { - /** @type {!Blockly.ShortcutRegistry.KeyboardShortcut} */ - const copyShortcut = { - name: Constants.SHORTCUT_NAMES.COPY, - preconditionFn: (workspace) => { - if ( - workspace.keyboardAccessibilityMode && - !workspace.options.readOnly - ) { - const curNode = workspace.getCursor().getCurNode(); - if (curNode && curNode.getSourceBlock()) { - const sourceBlock = curNode.getSourceBlock(); - return ( - !Blockly.Gesture.inProgress() && - sourceBlock && - sourceBlock.isDeletable() && - sourceBlock.isMovable() - ); - } - } - return false; - }, - callback: (workspace) => { - const sourceBlock = /** @type {Blockly.BlockSvg} */ ( - workspace.getCursor().getCurNode().getSourceBlock() - ); - workspace.hideChaff(); - this.copyData = sourceBlock.toCopyData(); - this.copyWorkspace = sourceBlock.workspace; - return !!this.copyData; - }, - }; - - Blockly.ShortcutRegistry.registry.register(copyShortcut); - - const ctrlC = Blockly.ShortcutRegistry.registry.createSerializedKey( - Blockly.utils.KeyCodes.C, - [Blockly.utils.KeyCodes.CTRL], - ); - Blockly.ShortcutRegistry.registry.addKeyMapping( - ctrlC, - copyShortcut.name, - true, - ); - - const altC = Blockly.ShortcutRegistry.registry.createSerializedKey( - Blockly.utils.KeyCodes.C, - [Blockly.utils.KeyCodes.ALT], - ); - Blockly.ShortcutRegistry.registry.addKeyMapping( - altC, - copyShortcut.name, - true, - ); - - const metaC = Blockly.ShortcutRegistry.registry.createSerializedKey( - Blockly.utils.KeyCodes.C, - [Blockly.utils.KeyCodes.META], - ); - Blockly.ShortcutRegistry.registry.addKeyMapping( - metaC, - copyShortcut.name, - true, - ); - } - - /** - * Register shortcut to paste the copied block to the marked location. - * @protected - */ - registerPaste() { - /** @type {!Blockly.ShortcutRegistry.KeyboardShortcut} */ - const pasteShortcut = { - name: Constants.SHORTCUT_NAMES.PASTE, - preconditionFn: (workspace) => { - return ( - workspace.keyboardAccessibilityMode && - !workspace.options.readOnly && - !Blockly.Gesture.inProgress() - ); - }, - callback: () => { - if (!this.copyData || !this.copyWorkspace) return false; - return this.navigation.paste(this.copyData, this.copyWorkspace); - }, - }; - - Blockly.ShortcutRegistry.registry.register(pasteShortcut); - - const ctrlV = Blockly.ShortcutRegistry.registry.createSerializedKey( - Blockly.utils.KeyCodes.V, - [Blockly.utils.KeyCodes.CTRL], - ); - Blockly.ShortcutRegistry.registry.addKeyMapping( - ctrlV, - pasteShortcut.name, - true, - ); - - const altV = Blockly.ShortcutRegistry.registry.createSerializedKey( - Blockly.utils.KeyCodes.V, - [Blockly.utils.KeyCodes.ALT], - ); - Blockly.ShortcutRegistry.registry.addKeyMapping( - altV, - pasteShortcut.name, - true, - ); - - const metaV = Blockly.ShortcutRegistry.registry.createSerializedKey( - Blockly.utils.KeyCodes.V, - [Blockly.utils.KeyCodes.META], - ); - Blockly.ShortcutRegistry.registry.addKeyMapping( - metaV, - pasteShortcut.name, - true, - ); - } - - /** - * Keyboard shortcut to copy and delete the block the cursor is on using - * ctrl+x, cmd+x, or alt+x. - * @protected - */ - registerCut() { - /** @type {!Blockly.ShortcutRegistry.KeyboardShortcut} */ - const cutShortcut = { - name: Constants.SHORTCUT_NAMES.CUT, - preconditionFn: (workspace) => { - if ( - workspace.keyboardAccessibilityMode && - !workspace.options.readOnly - ) { - const curNode = workspace.getCursor().getCurNode(); - if (curNode && curNode.getSourceBlock()) { - const sourceBlock = curNode.getSourceBlock(); - return ( - !Blockly.Gesture.inProgress() && - sourceBlock && - sourceBlock.isDeletable() && - sourceBlock.isMovable() && - !sourceBlock.workspace.isFlyout - ); - } - } - return false; - }, - callback: (workspace) => { - const sourceBlock = /** @type {Blockly.BlockSvg} */ ( - workspace.getCursor().getCurNode().getSourceBlock() - ); - this.copyData = sourceBlock.toCopyData(); - this.copyWorkspace = sourceBlock.workspace; - this.navigation.moveCursorOnBlockDelete(workspace, sourceBlock); - sourceBlock.checkAndDelete(); - return true; - }, - }; - - Blockly.ShortcutRegistry.registry.register(cutShortcut); - - const ctrlX = Blockly.ShortcutRegistry.registry.createSerializedKey( - Blockly.utils.KeyCodes.X, - [Blockly.utils.KeyCodes.CTRL], - ); - Blockly.ShortcutRegistry.registry.addKeyMapping( - ctrlX, - cutShortcut.name, - true, - ); - - const altX = Blockly.ShortcutRegistry.registry.createSerializedKey( - Blockly.utils.KeyCodes.X, - [Blockly.utils.KeyCodes.ALT], - ); - Blockly.ShortcutRegistry.registry.addKeyMapping( - altX, - cutShortcut.name, - true, - ); - - const metaX = Blockly.ShortcutRegistry.registry.createSerializedKey( - Blockly.utils.KeyCodes.X, - [Blockly.utils.KeyCodes.META], - ); - Blockly.ShortcutRegistry.registry.addKeyMapping( - metaX, - cutShortcut.name, - true, - ); - } - - /** - * Registers shortcut to delete the block the cursor is on using delete or - * backspace. - * @protected - */ - registerDelete() { - /** @type {!Blockly.ShortcutRegistry.KeyboardShortcut} */ - const deleteShortcut = { - name: Constants.SHORTCUT_NAMES.DELETE, - preconditionFn: function (workspace) { - if ( - workspace.keyboardAccessibilityMode && - !workspace.options.readOnly - ) { - const curNode = workspace.getCursor().getCurNode(); - if (curNode && curNode.getSourceBlock()) { - const sourceBlock = curNode.getSourceBlock(); - return sourceBlock && sourceBlock.isDeletable(); - } - } - return false; - }, - callback: (workspace, e) => { - const sourceBlock = workspace.getCursor().getCurNode().getSourceBlock(); - // Delete or backspace. - // Stop the browser from going back to the previous page. - // Do this first to prevent an error in the delete code from resulting - // in data loss. - e.preventDefault(); - // Don't delete while dragging. Jeez. - if (Blockly.Gesture.inProgress()) { - return false; - } - this.navigation.moveCursorOnBlockDelete(workspace, sourceBlock); - sourceBlock.checkAndDelete(); - return true; - }, - }; - Blockly.ShortcutRegistry.registry.register(deleteShortcut); - Blockly.ShortcutRegistry.registry.addKeyMapping( - Blockly.utils.KeyCodes.DELETE, - deleteShortcut.name, - true, - ); - Blockly.ShortcutRegistry.registry.addKeyMapping( - Blockly.utils.KeyCodes.BACKSPACE, - deleteShortcut.name, - true, - ); - } - - /** - * Registers all default keyboard shortcut items for keyboard navigation. This - * should be called once per instance of KeyboardShortcutRegistry. - * @protected - */ - registerDefaults() { - this.registerPrevious(); - this.registerNext(); - this.registerIn(); - this.registerOut(); - - this.registerDisconnect(); - this.registerExit(); - this.registerInsert(); - this.registerMark(); - this.registerToolboxFocus(); - this.registerToggleKeyboardNav(); - - this.registerWorkspaceMoveDown(); - this.registerWorkspaceMoveLeft(); - this.registerWorkspaceMoveUp(); - this.registerWorkspaceMoveRight(); - - this.registerCopy(); - this.registerPaste(); - this.registerCut(); - this.registerDelete(); - } - - /** - * Removes all the keyboard navigation shortcuts. - * @public - */ - dispose() { - const shortcutNames = Object.values(Constants.SHORTCUT_NAMES); - for (const name of shortcutNames) { - Blockly.ShortcutRegistry.registry.unregister(name); - } - this.removeShortcutHandlers(); - this.navigation.dispose(); - } -} diff --git a/plugins/keyboard-navigation/test/index.html b/plugins/keyboard-navigation/test/index.html deleted file mode 100644 index e0db0d325..000000000 --- a/plugins/keyboard-navigation/test/index.html +++ /dev/null @@ -1,53 +0,0 @@ - - - - - Blockly Plugin Test - - - - -

- Keyboard Navigation is our first step towards an accessible Blockly.
- For more information on how the default keyboard navigation works please - see the -
documentation. -
-
- - Cursors
- The cursor controls how the user navigates the blocks, inputs, fields and - connections on a workspace. This demo shows three different cursors:
- Default Cursor: This cursor uses previous, next, in, and out to - navigate through the different parts of a block. See the - developer documentation - for more information.
- Basic Cursor: Uses pre order traversal to allow users to navigate - through everything using only the previous and next command.
- Line Cursor: We tried to make this cursor mimic a text editor. - Navigating up and down will take the cursor to the next and previous - "line" of code. Navigating in and out will move the cursor through all the - fields and inputs in that "line" of code. -

- - - -
- - - diff --git a/plugins/keyboard-navigation/test/index.js b/plugins/keyboard-navigation/test/index.js deleted file mode 100644 index 919ab3103..000000000 --- a/plugins/keyboard-navigation/test/index.js +++ /dev/null @@ -1,83 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -/** - * @fileoverview Plugin test. - */ - -import {createPlayground} from '@blockly/dev-tools'; -import * as Blockly from 'blockly'; -import {toolbox} from './toolbox'; - -import {LineCursor, NavigationController} from '../src'; - -let controller; - -/** - * Create a workspace. - * @param {HTMLElement} blocklyDiv The blockly container div. - * @param {!Blockly.BlocklyOptions} options The Blockly options. - * @returns {!Blockly.WorkspaceSvg} The created workspace. - */ -function createWorkspace(blocklyDiv, options) { - const workspace = Blockly.inject(blocklyDiv, options); - controller.addWorkspace(workspace); - return workspace; -} - -document.addEventListener('DOMContentLoaded', function () { - controller = new NavigationController(); - controller.init(); - const defaultOptions = { - toolbox: toolbox, - }; - createPlayground( - document.getElementById('root'), - createWorkspace, - defaultOptions, - ); -}); - -document - .getElementById('accessibilityModeCheck') - .addEventListener('click', (e) => { - if (e.target.checked) { - controller.enable(Blockly.getMainWorkspace()); - } else { - controller.disable(Blockly.getMainWorkspace()); - } - }); - -document.getElementById('cursorChanger').addEventListener('change', (e) => { - const cursorType = e.target.value; - const accessibilityCheckbox = document.getElementById( - 'accessibilityModeCheck', - ); - const markerManager = Blockly.getMainWorkspace().getMarkerManager(); - const oldCurNode = markerManager.getCursor().getCurNode(); - - document.getElementById('cursorChanger').value = cursorType; - if (cursorType === 'basic') { - Blockly.ASTNode.NAVIGATE_ALL_FIELDS = false; - markerManager.setCursor(new Blockly.BasicCursor()); - } else if (cursorType === 'line') { - Blockly.ASTNode.NAVIGATE_ALL_FIELDS = true; - markerManager.setCursor(new LineCursor()); - } else { - Blockly.ASTNode.NAVIGATE_ALL_FIELDS = false; - markerManager.setCursor(new Blockly.Cursor()); - } - if (oldCurNode) { - markerManager.getCursor().setCurNode(oldCurNode); - } - - if (!accessibilityCheckbox.checked) { - accessibilityCheckbox.checked = true; - controller.enable(Blockly.getMainWorkspace()); - } - - document.activeElement.blur(); -}); diff --git a/plugins/keyboard-navigation/test/navigation_modify_test.mocha.js b/plugins/keyboard-navigation/test/navigation_modify_test.mocha.js deleted file mode 100644 index 492b98f9c..000000000 --- a/plugins/keyboard-navigation/test/navigation_modify_test.mocha.js +++ /dev/null @@ -1,691 +0,0 @@ -/** - * @license - * Copyright 2021 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -const chai = require('chai'); -const Blockly = require('blockly'); -const {Navigation} = require('../src/navigation'); -const assert = chai.assert; -const {testHelpers} = require('@blockly/dev-tools'); -const {captureWarnings} = testHelpers; - -suite('Insert/Modify', function () { - /** - * Check that modify failed. - * @param {Navigation} navigation The class under test. - * @param {Blockly.WorkspaceSvg} workspace The main workspace. - * @param {!Blockly.ASTNode} markerNode The node to try to connect to. - * @param {!Blockly.ASTNode} cursorNode The node to connect to the markerNode. - */ - function assertModifyFails(navigation, workspace, markerNode, cursorNode) { - let modifyResult; - const warnings = captureWarnings(function () { - modifyResult = navigation.tryToConnectMarkerAndCursor( - workspace, - markerNode, - cursorNode, - ); - }); - assert.isFalse(modifyResult); - assert.equal( - warnings.length, - 1, - 'Expecting 1 warnings for why modify failed.', - ); - } - - /** - * Define default blocks. - */ - function defineTestBlocks() { - Blockly.defineBlocksWithJsonArray([ - { - type: 'stack_block', - message0: '', - previousStatement: null, - nextStatement: null, - }, - { - type: 'row_block', - message0: '%1', - args0: [ - { - type: 'input_value', - name: 'INPUT', - }, - ], - output: null, - }, - { - type: 'statement_block', - message0: '%1', - args0: [ - { - type: 'input_statement', - name: 'NAME', - }, - ], - previousStatement: null, - nextStatement: null, - colour: 230, - tooltip: '', - helpUrl: '', - }, - ]); - } - - setup(function () { - this.jsdomCleanup = require('jsdom-global')( - '
', - ); - // We are running these tests in node even thought they require a rendered - // workspace, which doesn't exactly work. The rendering system expects - // cancelAnimationFrame to be defined so we need to define it. - window.cancelAnimationFrame = function () {}; - - // NOTE: block positions chosen such that they aren't unintentionally - // bumped out of bounds during tests. - const xmlText = ` - - - - - - - - - `; - - defineTestBlocks(); - - this.workspace = Blockly.inject('blocklyDiv', { - toolbox: ` - `, - }); - Blockly.Xml.domToWorkspace( - Blockly.utils.xml.textToDom(xmlText), - this.workspace, - ); - this.navigation = new Navigation(); - this.navigation.addWorkspace(this.workspace); - - this.stack_block_1 = this.workspace.getBlockById('stack_block_1'); - this.stack_block_2 = this.workspace.getBlockById('stack_block_2'); - this.row_block_1 = this.workspace.getBlockById('row_block_1'); - this.row_block_2 = this.workspace.getBlockById('row_block_2'); - this.statement_block_1 = this.workspace.getBlockById('statement_block_1'); - this.statement_block_2 = this.workspace.getBlockById('statement_block_2'); - this.navigation.enableKeyboardAccessibility(this.workspace); - }); - - teardown(function () { - delete Blockly.Blocks['stack_block']; - delete Blockly.Blocks['row_block']; - delete Blockly.Blocks['statement_block']; - window.cancelAnimationFrame = undefined; - this.jsdomCleanup(); - }); - - suite('Marked Connection', function () { - suite('Marker on next', function () { - setup(function () { - this.markerNode = Blockly.ASTNode.createConnectionNode( - this.stack_block_1.nextConnection, - ); - }); - test('Cursor on workspace', function () { - const cursorNode = Blockly.ASTNode.createWorkspaceNode( - this.workspace, - new Blockly.utils.Coordinate(0, 0), - ); - assertModifyFails( - this.navigation, - this.workspace, - this.markerNode, - cursorNode, - ); - }); - test('Cursor on compatible connection', function () { - const cursorNode = Blockly.ASTNode.createConnectionNode( - this.stack_block_2.previousConnection, - ); - assert.isTrue( - this.navigation.tryToConnectMarkerAndCursor( - this.workspace, - this.markerNode, - cursorNode, - ), - ); - assert.equal(this.stack_block_1.getNextBlock().id, 'stack_block_2'); - }); - test('Cursor on incompatible connection', function () { - // Connect method will try to find a way to connect blocks with - // incompatible types. - const cursorNode = Blockly.ASTNode.createConnectionNode( - this.stack_block_2.nextConnection, - ); - assert.isTrue( - this.navigation.tryToConnectMarkerAndCursor( - this.workspace, - this.markerNode, - cursorNode, - ), - ); - assert.equal(this.stack_block_1.getNextBlock(), this.stack_block_2); - }); - test('Cursor on really incompatible connection', function () { - const cursorNode = Blockly.ASTNode.createConnectionNode( - this.row_block_1.outputConnection, - ); - assertModifyFails( - this.navigation, - this.workspace, - this.markerNode, - cursorNode, - ); - assert.isNull(this.stack_block_1.getNextBlock()); - }); - test('Cursor on block', function () { - const cursorNode = Blockly.ASTNode.createBlockNode(this.stack_block_2); - assert.isTrue( - this.navigation.tryToConnectMarkerAndCursor( - this.workspace, - this.markerNode, - cursorNode, - ), - ); - assert.equal(this.stack_block_1.getNextBlock().id, 'stack_block_2'); - }); - }); - - suite('Marker on previous', function () { - setup(function () { - this.markerNode = Blockly.ASTNode.createConnectionNode( - this.stack_block_1.previousConnection, - ); - }); - test('Cursor on compatible connection', function () { - const cursorNode = Blockly.ASTNode.createConnectionNode( - this.stack_block_2.nextConnection, - ); - assert.isTrue( - this.navigation.tryToConnectMarkerAndCursor( - this.workspace, - this.markerNode, - cursorNode, - ), - ); - assert.equal(this.stack_block_1.getPreviousBlock().id, 'stack_block_2'); - }); - test('Cursor on incompatible connection', function () { - const cursorNode = Blockly.ASTNode.createConnectionNode( - this.stack_block_2.previousConnection, - ); - assertModifyFails( - this.navigation, - this.workspace, - this.markerNode, - cursorNode, - ); - assert.isNull(this.stack_block_1.getPreviousBlock()); - }); - test('Cursor on really incompatible connection', function () { - const cursorNode = Blockly.ASTNode.createConnectionNode( - this.row_block_1.outputConnection, - ); - assertModifyFails( - this.navigation, - this.workspace, - this.markerNode, - cursorNode, - ); - assert.isNull(this.stack_block_1.getNextBlock()); - }); - test('Cursor on block', function () { - const cursorNode = Blockly.ASTNode.createBlockNode(this.stack_block_2); - assert.isTrue( - this.navigation.tryToConnectMarkerAndCursor( - this.workspace, - this.markerNode, - cursorNode, - ), - ); - assert.equal(this.stack_block_1.getPreviousBlock().id, 'stack_block_2'); - }); - test('Cursor on incompatible block', function () { - const cursorNode = Blockly.ASTNode.createBlockNode(this.row_block_1); - assertModifyFails( - this.navigation, - this.workspace, - this.markerNode, - cursorNode, - ); - assert.isNull(this.stack_block_1.getPreviousBlock()); - }); - }); - - suite('Marker on value input', function () { - setup(function () { - this.markerNode = Blockly.ASTNode.createConnectionNode( - this.row_block_1.inputList[0].connection, - ); - }); - test('Cursor on compatible connection', function () { - const cursorNode = Blockly.ASTNode.createConnectionNode( - this.row_block_2.outputConnection, - ); - assert.isTrue( - this.navigation.tryToConnectMarkerAndCursor( - this.workspace, - this.markerNode, - cursorNode, - ), - ); - assert.equal(this.row_block_2.getParent().id, 'row_block_1'); - }); - test('Cursor on incompatible connection', function () { - // Connect method will try to find a way to connect blocks with - // incompatible types. - const cursorNode = Blockly.ASTNode.createConnectionNode( - this.row_block_2.inputList[0].connection, - ); - assert.isTrue( - this.navigation.tryToConnectMarkerAndCursor( - this.workspace, - this.markerNode, - cursorNode, - ), - ); - assert.equal( - this.row_block_1.inputList[0].connection.targetBlock(), - this.row_block_2, - ); - }); - test('Cursor on really incompatible connection', function () { - const cursorNode = Blockly.ASTNode.createConnectionNode( - this.stack_block_1.previousConnection, - ); - assertModifyFails( - this.navigation, - this.workspace, - this.markerNode, - cursorNode, - ); - }); - test('Cursor on block', function () { - const cursorNode = Blockly.ASTNode.createBlockNode(this.row_block_2); - assert.isTrue( - this.navigation.tryToConnectMarkerAndCursor( - this.workspace, - this.markerNode, - cursorNode, - ), - ); - assert.equal(this.row_block_2.getParent().id, 'row_block_1'); - }); - }); - - suite('Marked Statement input', function () { - setup(function () { - this.statement_block_1.inputList[0].connection.connect( - this.stack_block_1.previousConnection, - ); - this.stack_block_1.nextConnection.connect( - this.stack_block_2.previousConnection, - ); - this.markerNode = Blockly.ASTNode.createInputNode( - this.statement_block_1.inputList[0], - ); - }); - test('Cursor on block inside statement', function () { - const cursorNode = Blockly.ASTNode.createConnectionNode( - this.stack_block_2.previousConnection, - ); - assert.isTrue( - this.navigation.tryToConnectMarkerAndCursor( - this.workspace, - this.markerNode, - cursorNode, - ), - ); - assert.equal( - this.stack_block_2.previousConnection.targetBlock(), - this.statement_block_1, - ); - }); - test('Cursor on stack', function () { - const cursorNode = Blockly.ASTNode.createStackNode( - this.statement_block_2, - ); - assert.isTrue( - this.navigation.tryToConnectMarkerAndCursor( - this.workspace, - this.markerNode, - cursorNode, - ), - ); - assert.equal( - this.statement_block_2.getParent().id, - 'statement_block_1', - ); - }); - test('Cursor on incompatible type', function () { - const cursorNode = Blockly.ASTNode.createConnectionNode( - this.row_block_1.outputConnection, - ); - assertModifyFails( - this.navigation, - this.workspace, - this.markerNode, - cursorNode, - ); - assert.isNull(this.row_block_1.getParent()); - }); - }); - - suite('Marker on output', function () { - setup(function () { - this.markerNode = Blockly.ASTNode.createConnectionNode( - this.row_block_1.outputConnection, - ); - }); - test('Cursor on compatible connection', function () { - const cursorNode = Blockly.ASTNode.createConnectionNode( - this.row_block_2.inputList[0].connection, - ); - assert.isTrue( - this.navigation.tryToConnectMarkerAndCursor( - this.workspace, - this.markerNode, - cursorNode, - ), - ); - assert.equal(this.row_block_1.getParent().id, 'row_block_2'); - }); - test('Cursor on incompatible connection', function () { - const cursorNode = Blockly.ASTNode.createConnectionNode( - this.row_block_2.outputConnection, - ); - assertModifyFails( - this.navigation, - this.workspace, - this.markerNode, - cursorNode, - ); - }); - test('Cursor on really incompatible connection', function () { - const cursorNode = Blockly.ASTNode.createConnectionNode( - this.stack_block_1.previousConnection, - ); - assertModifyFails( - this.navigation, - this.workspace, - this.markerNode, - cursorNode, - ); - }); - test('Cursor on block', function () { - const cursorNode = Blockly.ASTNode.createBlockNode(this.row_block_2); - assert.isTrue( - this.navigation.tryToConnectMarkerAndCursor( - this.workspace, - this.markerNode, - cursorNode, - ), - ); - assert.equal(this.row_block_1.getParent().id, 'row_block_2'); - }); - }); - }); - - suite('Marked Workspace', function () { - setup(function () { - this.markerNode = Blockly.ASTNode.createWorkspaceNode( - this.workspace, - new Blockly.utils.Coordinate(100, 200), - ); - }); - test('Cursor on row block', function () { - const cursorNode = Blockly.ASTNode.createBlockNode(this.row_block_1); - assert.isTrue( - this.navigation.tryToConnectMarkerAndCursor( - this.workspace, - this.markerNode, - cursorNode, - ), - ); - const pos = this.row_block_1.getRelativeToSurfaceXY(); - assert.equal(pos.x, 100); - assert.equal(pos.y, 200); - }); - - test('Cursor on output connection', function () { - const cursorNode = Blockly.ASTNode.createConnectionNode( - this.row_block_1.outputConnection, - ); - assert.isTrue( - this.navigation.tryToConnectMarkerAndCursor( - this.workspace, - this.markerNode, - cursorNode, - ), - ); - const pos = this.row_block_1.getRelativeToSurfaceXY(); - assert.equal(pos.x, 100); - assert.equal(pos.y, 200); - }); - - test('Cursor on previous connection', function () { - const cursorNode = Blockly.ASTNode.createConnectionNode( - this.stack_block_1.previousConnection, - ); - assert.isTrue( - this.navigation.tryToConnectMarkerAndCursor( - this.workspace, - this.markerNode, - cursorNode, - ), - ); - const pos = this.stack_block_1.getRelativeToSurfaceXY(); - assert.equal(pos.x, 100); - assert.equal(pos.y, 200); - }); - - test('Cursor on input connection', function () { - // Move the source block to the marked location on the workspace. - const cursorNode = Blockly.ASTNode.createConnectionNode( - this.row_block_1.inputList[0].connection, - ); - assert.isTrue( - this.navigation.tryToConnectMarkerAndCursor( - this.workspace, - this.markerNode, - cursorNode, - ), - ); - }); - - test('Cursor on next connection', function () { - // Move the source block to the marked location on the workspace. - const cursorNode = Blockly.ASTNode.createConnectionNode( - this.stack_block_1.nextConnection, - ); - assert.isTrue( - this.navigation.tryToConnectMarkerAndCursor( - this.workspace, - this.markerNode, - cursorNode, - ), - ); - }); - - test('Cursor on child block (row)', function () { - this.row_block_1.inputList[0].connection.connect( - this.row_block_2.outputConnection, - ); - - const cursorNode = Blockly.ASTNode.createBlockNode(this.row_block_2); - assert.isTrue( - this.navigation.tryToConnectMarkerAndCursor( - this.workspace, - this.markerNode, - cursorNode, - ), - ); - assert.isNull(this.row_block_2.getParent()); - const pos = this.row_block_2.getRelativeToSurfaceXY(); - assert.equal(pos.x, 100); - assert.equal(pos.y, 200); - }); - - test('Cursor on child block (stack)', function () { - this.stack_block_1.nextConnection.connect( - this.stack_block_2.previousConnection, - ); - - const cursorNode = Blockly.ASTNode.createBlockNode(this.stack_block_2); - assert.isTrue( - this.navigation.tryToConnectMarkerAndCursor( - this.workspace, - this.markerNode, - cursorNode, - ), - ); - assert.isNull(this.stack_block_2.getParent()); - const pos = this.stack_block_2.getRelativeToSurfaceXY(); - assert.equal(pos.x, 100); - assert.equal(pos.y, 200); - }); - - test('Cursor on workspace', function () { - const cursorNode = Blockly.ASTNode.createWorkspaceNode( - this.workspace, - new Blockly.utils.Coordinate(100, 100), - ); - assertModifyFails( - this.navigation, - this.workspace, - this.markerNode, - cursorNode, - ); - }); - }); - - suite('Marked Block', function () { - suite('Marked any block', function () { - // These tests are using a stack block, but do not depend on the type of - // the block. - setup(function () { - this.markerNode = Blockly.ASTNode.createBlockNode(this.stack_block_1); - }); - test('Cursor on workspace', function () { - const cursorNode = Blockly.ASTNode.createWorkspaceNode( - this.workspace, - new Blockly.utils.Coordinate(100, 100), - ); - assertModifyFails( - this.navigation, - this.workspace, - this.markerNode, - cursorNode, - ); - }); - }); - suite('Marked stack block', function () { - setup(function () { - this.markerNode = Blockly.ASTNode.createBlockNode(this.stack_block_1); - }); - test('Cursor on row block', function () { - const cursorNode = Blockly.ASTNode.createBlockNode(this.row_block_1); - assertModifyFails( - this.navigation, - this.workspace, - this.markerNode, - cursorNode, - ); - }); - test('Cursor on stack block', function () { - const cursorNode = Blockly.ASTNode.createBlockNode(this.stack_block_1); - assertModifyFails( - this.navigation, - this.workspace, - this.markerNode, - cursorNode, - ); - }); - test('Cursor on next connection', function () { - const cursorNode = Blockly.ASTNode.createConnectionNode( - this.stack_block_2.nextConnection, - ); - assertModifyFails( - this.navigation, - this.workspace, - this.markerNode, - cursorNode, - ); - }); - test('Cursor on previous connection', function () { - const cursorNode = Blockly.ASTNode.createConnectionNode( - this.stack_block_2.previousConnection, - ); - assertModifyFails( - this.navigation, - this.workspace, - this.markerNode, - cursorNode, - ); - }); - }); - suite('Marked row block', function () { - setup(function () { - this.markerNode = Blockly.ASTNode.createBlockNode(this.row_block_1); - }); - test('Cursor on stack block', function () { - const cursorNode = Blockly.ASTNode.createBlockNode(this.stack_block_1); - assertModifyFails( - this.navigation, - this.workspace, - this.markerNode, - cursorNode, - ); - }); - test('Cursor on row block', function () { - const cursorNode = Blockly.ASTNode.createBlockNode(this.row_block_1); - assertModifyFails( - this.navigation, - this.workspace, - this.markerNode, - cursorNode, - ); - }); - test('Cursor on value input connection', function () { - const cursorNode = Blockly.ASTNode.createConnectionNode( - this.row_block_2.inputList[0].connection, - ); - assertModifyFails( - this.navigation, - this.workspace, - this.markerNode, - cursorNode, - ); - }); - test('Cursor on output connection', function () { - const cursorNode = Blockly.ASTNode.createConnectionNode( - this.row_block_2.outputConnection, - ); - assertModifyFails( - this.navigation, - this.workspace, - this.markerNode, - cursorNode, - ); - }); - }); - }); -}); diff --git a/plugins/keyboard-navigation/test/navigation_test.mocha.js b/plugins/keyboard-navigation/test/navigation_test.mocha.js deleted file mode 100644 index 81705b850..000000000 --- a/plugins/keyboard-navigation/test/navigation_test.mocha.js +++ /dev/null @@ -1,1368 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -/** - * @fileoverview - * @author aschmiedt@google.com (Abby Schmiedt) - */ -'use strict'; - -const chai = require('chai'); -const sinon = require('sinon'); - -const Blockly = require('blockly'); -const {NavigationController, Constants} = require('../src/index'); -const { - createNavigationWorkspace, - createKeyDownEvent, -} = require('./test_helper'); - -suite('Navigation', function () { - setup(function () { - this.jsdomCleanup = require('jsdom-global')( - '
', - ); - // We are running these tests in node even thought they require a rendered - // workspace, which doesn't exactly work. The rendering system expects - // cancelAnimationFrame to be defined so we need to define it. - window.cancelAnimationFrame = function () {}; - this.controller = new NavigationController(); - this.controller.init(); - this.navigation = this.controller.navigation; - - this.getContextStub = sinon - .stub(window.HTMLCanvasElement.prototype, 'getContext') - .callsFake(() => { - return { - measureText: function () { - return {width: 0}; - }, - }; - }); - }); - - teardown(function () { - this.controller.dispose(); - window.cancelAnimationFrame = undefined; - this.jsdomCleanup(); - sinon.restore(); - }); - - // Test that toolbox key handlers call through to the right functions and - // transition correctly between toolbox, workspace, and flyout. - suite('Tests toolbox keys', function () { - setup(function () { - Blockly.defineBlocksWithJsonArray([ - { - type: 'basic_block', - message0: '%1', - args0: [ - { - type: 'field_input', - name: 'TEXTFIELD', - text: 'test', - }, - ], - }, - ]); - this.workspace = createNavigationWorkspace(this.navigation, true); - this.navigation.focusToolbox(this.workspace); - }); - - teardown(function () { - this.navigation.removeWorkspace(this.workspace); - this.workspace.dispose(); - sinon.restore(); - delete Blockly.Blocks['basic_block']; - }); - - const testCases = [ - [ - 'Calls toolbox selectNext', - createKeyDownEvent(Blockly.utils.KeyCodes.S, 'NotAField'), - 'selectNext', - ], - [ - 'Calls toolbox selectPrevious', - createKeyDownEvent(Blockly.utils.KeyCodes.W, 'NotAField'), - 'selectPrevious', - ], - [ - 'Calls toolbox selectParent', - createKeyDownEvent(Blockly.utils.KeyCodes.D, 'NotAField'), - 'selectChild', - ], - [ - 'Calls toolbox selectChild', - createKeyDownEvent(Blockly.utils.KeyCodes.A, 'NotAField'), - 'selectParent', - ], - ]; - - testCases.forEach(function (testCase) { - const testCaseName = testCase[0]; - const mockEvent = testCase[1]; - const stubName = testCase[2]; - test(testCaseName, function () { - const toolbox = this.workspace.getToolbox(); - const selectStub = sinon.stub(toolbox, stubName); - toolbox.selectedItem_ = toolbox.contents.values().next().value; - Blockly.ShortcutRegistry.registry.onKeyDown(this.workspace, mockEvent); - sinon.assert.called(selectStub); - }); - }); - - test('Go to flyout', function () { - const navigation = this.navigation; - const mockEvent = createKeyDownEvent( - Blockly.utils.KeyCodes.D, - 'NotAField', - ); - const keyDownSpy = sinon.spy( - Blockly.ShortcutRegistry.registry, - 'onKeyDown', - ); - - Blockly.ShortcutRegistry.registry.onKeyDown(this.workspace, mockEvent); - - chai.assert.isTrue(keyDownSpy.returned(true)); - chai.assert.equal( - navigation.getState(this.workspace), - Constants.STATE.FLYOUT, - ); - - const flyoutCursor = navigation.getFlyoutCursor(this.workspace); - // See test_helper.js for hardcoded field values. - chai.assert.equal( - flyoutCursor.getCurNode().getLocation().getFieldValue('TEXTFIELD'), - 'first', - ); - }); - - test('Focuses workspace from toolbox (e)', function () { - const navigation = this.navigation; - navigation.setState(this.workspace, Constants.STATE.TOOLBOX); - const mockEvent = createKeyDownEvent( - Blockly.utils.KeyCodes.E, - 'NotAField', - ); - const keyDownSpy = sinon.spy( - Blockly.ShortcutRegistry.registry, - 'onKeyDown', - ); - - Blockly.ShortcutRegistry.registry.onKeyDown(this.workspace, mockEvent); - - chai.assert.isTrue(keyDownSpy.returned(true)); - chai.assert.equal( - navigation.getState(this.workspace), - Constants.STATE.WORKSPACE, - ); - }); - test('Focuses workspace from toolbox (escape)', function () { - const navigation = this.navigation; - navigation.setState(this.workspace, Constants.STATE.TOOLBOX); - const mockEvent = createKeyDownEvent( - Blockly.utils.KeyCodes.ESC, - 'NotAField', - ); - const keyDownSpy = sinon.spy( - Blockly.ShortcutRegistry.registry, - 'onKeyDown', - ); - - Blockly.ShortcutRegistry.registry.onKeyDown(this.workspace, mockEvent); - - chai.assert.isTrue(keyDownSpy.returned(true)); - chai.assert.equal( - navigation.getState(this.workspace), - Constants.STATE.WORKSPACE, - ); - }); - }); - - // Test that flyout key handlers call through to the right functions and - // transition correctly between toolbox, workspace, and flyout. - suite('Tests flyout keys', function () { - setup(function () { - Blockly.defineBlocksWithJsonArray([ - { - type: 'basic_block', - message0: '%1', - args0: [ - { - type: 'field_input', - name: 'TEXTFIELD', - text: 'test', - }, - ], - }, - ]); - this.workspace = createNavigationWorkspace(this.navigation, true); - this.navigation.focusToolbox(this.workspace); - this.navigation.focusFlyout(this.workspace); - }); - - teardown(function () { - this.navigation.removeWorkspace(this.workspace); - this.workspace.dispose(); - sinon.restore(); - delete Blockly.Blocks['basic_block']; - }); - // Should be a no-op - test('Previous at beginning', function () { - const mockEvent = createKeyDownEvent( - Blockly.utils.KeyCodes.W, - 'NotAField', - ); - const keyDownSpy = sinon.spy( - Blockly.ShortcutRegistry.registry, - 'onKeyDown', - ); - - Blockly.ShortcutRegistry.registry.onKeyDown(this.workspace, mockEvent); - - chai.assert.isTrue(keyDownSpy.returned(true)); - chai.assert.equal( - this.navigation.getState(this.workspace), - Constants.STATE.FLYOUT, - ); - // See test_helper.js for hardcoded field values. - chai.assert.equal( - this.navigation - .getFlyoutCursor(this.workspace) - .getCurNode() - .getLocation() - .getFieldValue('TEXTFIELD'), - 'first', - ); - }); - test('Previous', function () { - const flyoutBlocks = this.workspace - .getFlyout() - .getWorkspace() - .getTopBlocks(); - this.navigation - .getFlyoutCursor(this.workspace) - .setCurNode(Blockly.ASTNode.createStackNode(flyoutBlocks[1])); - let flyoutBlock = this.navigation - .getFlyoutCursor(this.workspace) - .getCurNode() - .getLocation(); - // See test_helper.js for hardcoded field values. - chai.assert.equal(flyoutBlock.getFieldValue('TEXTFIELD'), 'second'); - - const mockEvent = createKeyDownEvent( - Blockly.utils.KeyCodes.W, - 'NotAField', - ); - const keyDownSpy = sinon.spy( - Blockly.ShortcutRegistry.registry, - 'onKeyDown', - ); - - Blockly.ShortcutRegistry.registry.onKeyDown(this.workspace, mockEvent); - - chai.assert.isTrue(keyDownSpy.returned(true)); - chai.assert.equal( - this.navigation.getState(this.workspace), - Constants.STATE.FLYOUT, - ); - flyoutBlock = this.navigation - .getFlyoutCursor(this.workspace) - .getCurNode() - .getLocation(); - // See test_helper.js for hardcoded field values. - chai.assert.equal(flyoutBlock.getFieldValue('TEXTFIELD'), 'first'); - }); - - test('Next', function () { - const mockEvent = createKeyDownEvent( - Blockly.utils.KeyCodes.S, - 'NotAField', - ); - const keyDownSpy = sinon.spy( - Blockly.ShortcutRegistry.registry, - 'onKeyDown', - ); - - Blockly.ShortcutRegistry.registry.onKeyDown(this.workspace, mockEvent); - - chai.assert.isTrue(keyDownSpy.returned(true)); - chai.assert.equal( - this.navigation.getState(this.workspace), - Constants.STATE.FLYOUT, - ); - const flyoutBlock = this.navigation - .getFlyoutCursor(this.workspace) - .getCurNode() - .getLocation(); - // See test_helper.js for hardcoded field values. - chai.assert.equal(flyoutBlock.getFieldValue('TEXTFIELD'), 'second'); - }); - - test('Out', function () { - const mockEvent = createKeyDownEvent( - Blockly.utils.KeyCodes.A, - 'NotAField', - ); - const keyDownSpy = sinon.spy( - Blockly.ShortcutRegistry.registry, - 'onKeyDown', - ); - - Blockly.ShortcutRegistry.registry.onKeyDown(this.workspace, mockEvent); - - chai.assert.isTrue(keyDownSpy.returned(true)); - chai.assert.equal( - this.navigation.getState(this.workspace), - Constants.STATE.TOOLBOX, - ); - }); - - test('Mark', function () { - const mockEvent = createKeyDownEvent( - Blockly.utils.KeyCodes.ENTER, - 'NotAField', - ); - const keyDownSpy = sinon.spy( - Blockly.ShortcutRegistry.registry, - 'onKeyDown', - ); - - Blockly.ShortcutRegistry.registry.onKeyDown(this.workspace, mockEvent); - - chai.assert.isTrue(keyDownSpy.returned(true)); - chai.assert.equal( - this.navigation.getState(this.workspace), - Constants.STATE.WORKSPACE, - ); - chai.assert.equal(this.workspace.getTopBlocks().length, 1); - }); - - test('Mark - Disabled Block', function () { - this.navigation.loggingCallback = function (type, msg) { - chai.assert.equal(msg, "Can't insert a disabled block."); - }; - const flyout = this.workspace.getFlyout(); - const topBlock = flyout.getWorkspace().getTopBlocks()[0]; - topBlock.setEnabled(false); - const mockEvent = createKeyDownEvent( - Blockly.utils.KeyCodes.ENTER, - 'NotAField', - ); - const keyDownSpy = sinon.spy( - Blockly.ShortcutRegistry.registry, - 'onKeyDown', - ); - - Blockly.ShortcutRegistry.registry.onKeyDown(this.workspace, mockEvent); - - chai.assert.isTrue(keyDownSpy.returned(true)); - chai.assert.equal( - this.navigation.getState(this.workspace), - Constants.STATE.FLYOUT, - ); - chai.assert.equal(this.workspace.getTopBlocks().length, 0); - this.navigation.loggingCallback = null; - }); - - test('Exit', function () { - const mockEvent = createKeyDownEvent( - Blockly.utils.KeyCodes.ESC, - 'NotAField', - ); - const keyDownSpy = sinon.spy( - Blockly.ShortcutRegistry.registry, - 'onKeyDown', - ); - - Blockly.ShortcutRegistry.registry.onKeyDown(this.workspace, mockEvent); - - chai.assert.isTrue(keyDownSpy.returned(true)); - chai.assert.equal( - this.navigation.getState(this.workspace), - Constants.STATE.WORKSPACE, - ); - }); - }); - // Test that workspace key handlers call through to the right functions and - // transition correctly between toolbox, workspace, and flyout. - suite('Tests workspace keys', function () { - setup(function () { - Blockly.defineBlocksWithJsonArray([ - { - type: 'basic_block', - message0: '%1', - args0: [ - { - type: 'field_input', - name: 'TEXTFIELD', - text: 'test', - }, - ], - previousStatement: null, - nextStatement: null, - }, - ]); - this.workspace = createNavigationWorkspace(this.navigation, true); - this.basicBlock = this.workspace.newBlock('basic_block'); - }); - - teardown(function () { - this.navigation.removeWorkspace(this.workspace); - this.workspace.dispose(); - sinon.restore(); - delete Blockly.Blocks['basic_block']; - }); - - test('Previous', function () { - const prevSpy = sinon.spy(this.workspace.getCursor(), 'prev'); - const keyDownSpy = sinon.spy( - Blockly.ShortcutRegistry.registry, - 'onKeyDown', - ); - const wEvent = createKeyDownEvent(Blockly.utils.KeyCodes.W, ''); - - Blockly.ShortcutRegistry.registry.onKeyDown(this.workspace, wEvent); - - chai.assert.isTrue(keyDownSpy.returned(true)); - sinon.assert.calledOnce(prevSpy); - chai.assert.equal( - this.navigation.getState(this.workspace), - Constants.STATE.WORKSPACE, - ); - }); - - test('Next', function () { - const nextSpy = sinon.spy(this.workspace.getCursor(), 'next'); - const keyDownSpy = sinon.spy( - Blockly.ShortcutRegistry.registry, - 'onKeyDown', - ); - const sEvent = createKeyDownEvent(Blockly.utils.KeyCodes.S, ''); - - Blockly.ShortcutRegistry.registry.onKeyDown(this.workspace, sEvent); - - chai.assert.isTrue(keyDownSpy.returned(true)); - sinon.assert.calledOnce(nextSpy); - chai.assert.equal( - this.navigation.getState(this.workspace), - Constants.STATE.WORKSPACE, - ); - }); - - test('Out', function () { - const outSpy = sinon.spy(this.workspace.getCursor(), 'out'); - const keyDownSpy = sinon.spy( - Blockly.ShortcutRegistry.registry, - 'onKeyDown', - ); - const aEvent = createKeyDownEvent(Blockly.utils.KeyCodes.A, ''); - - Blockly.ShortcutRegistry.registry.onKeyDown(this.workspace, aEvent); - - chai.assert.isTrue(keyDownSpy.returned(true)); - sinon.assert.calledOnce(outSpy); - chai.assert.equal( - this.navigation.getState(this.workspace), - Constants.STATE.WORKSPACE, - ); - }); - - test('In', function () { - const inSpy = sinon.spy(this.workspace.getCursor(), 'in'); - const keyDownSpy = sinon.spy( - Blockly.ShortcutRegistry.registry, - 'onKeyDown', - ); - const dEvent = createKeyDownEvent(Blockly.utils.KeyCodes.D, ''); - - Blockly.ShortcutRegistry.registry.onKeyDown(this.workspace, dEvent); - - chai.assert.isTrue(keyDownSpy.returned(true)); - sinon.assert.calledOnce(inSpy); - chai.assert.equal( - this.navigation.getState(this.workspace), - Constants.STATE.WORKSPACE, - ); - }); - - test('Insert', function () { - const blockNode = Blockly.ASTNode.createBlockNode(this.basicBlock); - this.navigation.getMarker(this.workspace).setCurNode(blockNode); - // Stub modify as we are not testing its behavior, only if it was called. - // Otherwise, there is a warning because there is no marked node. - const modifyStub = sinon - .stub(this.navigation, 'tryToConnectMarkerAndCursor') - .returns(true); - const keyDownSpy = sinon.spy( - Blockly.ShortcutRegistry.registry, - 'onKeyDown', - ); - const iEvent = createKeyDownEvent(Blockly.utils.KeyCodes.I, ''); - - Blockly.ShortcutRegistry.registry.onKeyDown(this.workspace, iEvent); - - chai.assert.isTrue(keyDownSpy.returned(true)); - sinon.assert.calledOnce(modifyStub); - chai.assert.equal( - this.navigation.getState(this.workspace), - Constants.STATE.WORKSPACE, - ); - }); - - test('Mark', function () { - this.workspace - .getCursor() - .setCurNode( - Blockly.ASTNode.createConnectionNode( - this.basicBlock.previousConnection, - ), - ); - const keyDownSpy = sinon.spy( - Blockly.ShortcutRegistry.registry, - 'onKeyDown', - ); - const enterEvent = createKeyDownEvent(Blockly.utils.KeyCodes.ENTER, ''); - - Blockly.ShortcutRegistry.registry.onKeyDown(this.workspace, enterEvent); - - const markedNode = this.workspace - .getMarker(this.navigation.MARKER_NAME) - .getCurNode(); - chai.assert.isTrue(keyDownSpy.returned(true)); - chai.assert.equal( - markedNode.getLocation(), - this.basicBlock.previousConnection, - ); - chai.assert.equal( - this.navigation.getState(this.workspace), - Constants.STATE.WORKSPACE, - ); - }); - - test('Toolbox', function () { - const keyDownSpy = sinon.spy( - Blockly.ShortcutRegistry.registry, - 'onKeyDown', - ); - const tEvent = createKeyDownEvent(Blockly.utils.KeyCodes.T, ''); - - Blockly.ShortcutRegistry.registry.onKeyDown(this.workspace, tEvent); - - const firstCategory = this.workspace - .getToolbox() - .contents.values() - .next().value; - chai.assert.isTrue(keyDownSpy.returned(true)); - chai.assert.equal( - this.workspace.getToolbox().getSelectedItem(), - firstCategory, - ); - chai.assert.equal( - this.navigation.getState(this.workspace), - Constants.STATE.TOOLBOX, - ); - }); - }); - - suite('Test key press', function () { - setup(function () { - Blockly.defineBlocksWithJsonArray([ - { - type: 'basic_block', - message0: '%1', - args0: [ - { - type: 'field_input', - name: 'TEXTFIELD', - text: 'test', - }, - ], - }, - ]); - this.workspace = createNavigationWorkspace(this.navigation, true); - - this.workspace.getCursor().drawer_ = null; - this.basicBlock = this.workspace.newBlock('basic_block'); - this.basicBlock.initSvg(); - this.basicBlock.render(); - }); - teardown(function () { - this.navigation.removeWorkspace(this.workspace); - this.workspace.dispose(); - sinon.restore(); - delete Blockly.Blocks['basic_block']; - }); - - test('Action does not exist', function () { - const block = this.workspace.getTopBlocks()[0]; - const field = block.inputList[0].fieldRow[0]; - const fieldSpy = sinon.spy(field, 'onShortcut'); - const mockEvent = createKeyDownEvent(Blockly.utils.KeyCodes.N, ''); - const keyDownSpy = sinon.spy( - Blockly.ShortcutRegistry.registry, - 'onKeyDown', - ); - this.workspace - .getCursor() - .setCurNode(Blockly.ASTNode.createFieldNode(field)); - - Blockly.ShortcutRegistry.registry.onKeyDown(this.workspace, mockEvent); - - chai.assert.isFalse(keyDownSpy.returned(true)); - sinon.assert.notCalled(fieldSpy); - }); - - test('Action exists - field handles action', function () { - const block = this.workspace.getTopBlocks()[0]; - const field = block.inputList[0].fieldRow[0]; - const mockEvent = createKeyDownEvent(Blockly.utils.KeyCodes.A, ''); - const fieldSpy = sinon.stub(field, 'onShortcut').returns(true); - const keyDownSpy = sinon.spy( - Blockly.ShortcutRegistry.registry, - 'onKeyDown', - ); - this.workspace - .getCursor() - .setCurNode(Blockly.ASTNode.createFieldNode(field)); - - Blockly.ShortcutRegistry.registry.onKeyDown(this.workspace, mockEvent); - - chai.assert.isTrue(keyDownSpy.returned(true)); - sinon.assert.calledOnce(fieldSpy); - }); - - test('Action exists - field does not handle action', function () { - const block = this.workspace.getTopBlocks()[0]; - const field = block.inputList[0].fieldRow[0]; - const mockEvent = createKeyDownEvent(Blockly.utils.KeyCodes.A, ''); - const fieldSpy = sinon.spy(field, 'onShortcut'); - const keyDownSpy = sinon.spy( - Blockly.ShortcutRegistry.registry, - 'onKeyDown', - ); - this.workspace - .getCursor() - .setCurNode(Blockly.ASTNode.createFieldNode(field)); - - Blockly.ShortcutRegistry.registry.onKeyDown(this.workspace, mockEvent); - - chai.assert.isTrue(keyDownSpy.returned(true)); - sinon.assert.calledOnce(fieldSpy); - }); - - test('Toggle Action Off', function () { - const mockEvent = createKeyDownEvent(Blockly.utils.KeyCodes.K, '', [ - Blockly.utils.KeyCodes.SHIFT, - Blockly.utils.KeyCodes.CTRL, - ]); - const keyDownSpy = sinon.spy( - Blockly.ShortcutRegistry.registry, - 'onKeyDown', - ); - this.workspace.keyboardAccessibilityMode = true; - - Blockly.ShortcutRegistry.registry.onKeyDown(this.workspace, mockEvent); - - chai.assert.isTrue(keyDownSpy.returned(true)); - chai.assert.isFalse(this.workspace.keyboardAccessibilityMode); - }); - - test('Toggle Action On', function () { - const mockEvent = createKeyDownEvent(Blockly.utils.KeyCodes.K, '', [ - Blockly.utils.KeyCodes.SHIFT, - Blockly.utils.KeyCodes.CTRL, - ]); - const keyDownSpy = sinon.spy( - Blockly.ShortcutRegistry.registry, - 'onKeyDown', - ); - this.workspace.keyboardAccessibilityMode = false; - - Blockly.ShortcutRegistry.registry.onKeyDown(this.workspace, mockEvent); - - chai.assert.isTrue(keyDownSpy.returned(true)); - chai.assert.isTrue(this.workspace.keyboardAccessibilityMode); - }); - - suite('Test key press in read only mode', function () { - setup(function () { - Blockly.defineBlocksWithJsonArray([ - { - type: 'field_block', - message0: '%1 %2', - args0: [ - { - type: 'field_input', - name: 'TEXTFIELD', - text: 'test', - }, - { - type: 'input_value', - name: 'NAME', - }, - ], - previousStatement: null, - nextStatement: null, - colour: 230, - tooltip: '', - helpUrl: '', - }, - ]); - this.workspace = createNavigationWorkspace(this.navigation, true, true); - Blockly.common.setMainWorkspace(this.workspace); - this.workspace.getCursor().drawer_ = null; - - this.fieldBlock1 = this.workspace.newBlock('field_block'); - this.fieldBlock1.initSvg(); - this.fieldBlock1.render(); - }); - - teardown(function () { - this.navigation.removeWorkspace(this.workspace); - this.workspace.dispose(); - sinon.restore(); - delete Blockly.Blocks['field_block']; - }); - - test('Perform valid action for read only', function () { - const astNode = Blockly.ASTNode.createBlockNode(this.fieldBlock1); - const mockEvent = createKeyDownEvent(Blockly.utils.KeyCodes.S, ''); - this.workspace.getCursor().setCurNode(astNode); - const keyDownSpy = sinon.spy( - Blockly.ShortcutRegistry.registry, - 'onKeyDown', - ); - - Blockly.ShortcutRegistry.registry.onKeyDown(this.workspace, mockEvent); - - chai.assert.isTrue(keyDownSpy.returned(true)); - }); - - test('Perform invalid action for read only', function () { - const astNode = Blockly.ASTNode.createBlockNode(this.fieldBlock1); - const mockEvent = createKeyDownEvent(Blockly.utils.KeyCodes.I, ''); - this.workspace.getCursor().setCurNode(astNode); - const keyDownSpy = sinon.spy( - Blockly.ShortcutRegistry.registry, - 'onKeyDown', - ); - - Blockly.ShortcutRegistry.registry.onKeyDown(this.workspace, mockEvent); - - chai.assert.isTrue(keyDownSpy.returned(false)); - }); - - test('Try to perform action on a field', function () { - const field = this.fieldBlock1.inputList[0].fieldRow[0]; - const astNode = Blockly.ASTNode.createFieldNode(field); - const mockEvent = createKeyDownEvent(Blockly.utils.KeyCodes.ENTER, ''); - this.workspace.getCursor().setCurNode(astNode); - const keyDownSpy = sinon.spy( - Blockly.ShortcutRegistry.registry, - 'onKeyDown', - ); - - Blockly.ShortcutRegistry.registry.onKeyDown(this.workspace, mockEvent); - - chai.assert.isTrue(keyDownSpy.returned(false)); - }); - }); - }); - suite('Insert Functions', function () { - setup(function () { - Blockly.defineBlocksWithJsonArray([ - { - type: 'basic_block', - message0: '%1', - args0: [ - { - type: 'field_input', - name: 'TEXTFIELD', - text: 'test', - }, - ], - previousStatement: null, - nextStatement: null, - }, - ]); - - this.workspace = createNavigationWorkspace(this.navigation, true); - - const basicBlock = this.workspace.newBlock('basic_block'); - const basicBlock2 = this.workspace.newBlock('basic_block'); - - this.basicBlock = basicBlock; - this.basicBlock2 = basicBlock2; - }); - - teardown(function () { - this.navigation.removeWorkspace(this.workspace); - this.workspace.dispose(); - sinon.restore(); - delete Blockly.Blocks['basic_block']; - }); - - test('Insert from flyout with a valid connection marked', function () { - const previousConnection = this.basicBlock.previousConnection; - const prevNode = Blockly.ASTNode.createConnectionNode(previousConnection); - this.workspace - .getMarker(this.navigation.MARKER_NAME) - .setCurNode(prevNode); - - this.navigation.focusToolbox(this.workspace); - this.navigation.focusFlyout(this.workspace); - this.navigation.insertFromFlyout(this.workspace); - - const insertedBlock = this.basicBlock.previousConnection.targetBlock(); - - chai.assert.isTrue(insertedBlock !== null); - chai.assert.equal( - this.navigation.getState(this.workspace), - Constants.STATE.WORKSPACE, - ); - }); - - test('Insert Block from flyout without marking a connection', function () { - this.navigation.focusToolbox(this.workspace); - this.navigation.focusFlyout(this.workspace); - this.navigation.insertFromFlyout(this.workspace); - - const numBlocks = this.workspace.getTopBlocks().length; - - // Make sure the block was not connected to anything - chai.assert.isNull(this.basicBlock.previousConnection.targetConnection); - chai.assert.isNull(this.basicBlock.nextConnection.targetConnection); - - // Make sure that the block was added to the workspace - chai.assert.equal(numBlocks, 3); - chai.assert.equal( - this.navigation.getState(this.workspace), - Constants.STATE.WORKSPACE, - ); - }); - - test('Connect two blocks that are on the workspace', function () { - const targetNode = Blockly.ASTNode.createConnectionNode( - this.basicBlock.previousConnection, - ); - const sourceNode = Blockly.ASTNode.createConnectionNode( - this.basicBlock2.nextConnection, - ); - - this.navigation.tryToConnectMarkerAndCursor( - this.workspace, - targetNode, - sourceNode, - ); - const insertedBlock = this.basicBlock.previousConnection.targetBlock(); - - chai.assert.isNotNull(insertedBlock); - }); - }); - suite('Connect Blocks', function () { - setup(function () { - Blockly.defineBlocksWithJsonArray([ - { - type: 'basic_block', - message0: '', - previousStatement: null, - nextStatement: null, - }, - { - type: 'inline_block', - message0: '%1 %2', - args0: [ - { - type: 'input_value', - name: 'NAME', - }, - { - type: 'input_value', - name: 'NAME', - }, - ], - inputsInline: true, - output: null, - tooltip: '', - helpUrl: '', - }, - ]); - - this.workspace = createNavigationWorkspace(this.navigation, true); - - const basicBlock = this.workspace.newBlock('basic_block'); - const basicBlock2 = this.workspace.newBlock('basic_block'); - const basicBlock3 = this.workspace.newBlock('basic_block'); - const basicBlock4 = this.workspace.newBlock('basic_block'); - - const inlineBlock1 = this.workspace.newBlock('inline_block'); - const inlineBlock2 = this.workspace.newBlock('inline_block'); - const inlineBlock3 = this.workspace.newBlock('inline_block'); - - this.basicBlock = basicBlock; - this.basicBlock2 = basicBlock2; - this.basicBlock3 = basicBlock3; - this.basicBlock4 = basicBlock4; - - this.inlineBlock1 = inlineBlock1; - this.inlineBlock2 = inlineBlock2; - this.inlineBlock3 = inlineBlock3; - - this.basicBlock.nextConnection.connect( - this.basicBlock2.previousConnection, - ); - - this.basicBlock3.nextConnection.connect( - this.basicBlock4.previousConnection, - ); - - this.inlineBlock1.inputList[0].connection.connect( - this.inlineBlock2.outputConnection, - ); - }); - - teardown(function () { - this.navigation.removeWorkspace(this.workspace); - this.workspace.dispose(); - sinon.restore(); - delete Blockly.Blocks['basic_block']; - delete Blockly.Blocks['inline_block']; - }); - - test('Connect cursor on previous into stack', function () { - const markedLocation = this.basicBlock2.previousConnection; - const cursorLocation = this.basicBlock3.previousConnection; - - this.navigation.connect(cursorLocation, markedLocation); - - chai.assert.equal( - this.basicBlock.nextConnection.targetBlock(), - this.basicBlock3, - ); - chai.assert.equal( - this.basicBlock2.previousConnection.targetBlock(), - this.basicBlock4, - ); - }); - - test('Connect marker on previous into stack', function () { - const markedLocation = this.basicBlock3.previousConnection; - const cursorLocation = this.basicBlock2.previousConnection; - - this.navigation.connect(cursorLocation, markedLocation); - - chai.assert.equal( - this.basicBlock.nextConnection.targetBlock(), - this.basicBlock3, - ); - chai.assert.equal( - this.basicBlock2.previousConnection.targetBlock(), - this.basicBlock4, - ); - }); - - test('Connect cursor on next into stack', function () { - const markedLocation = this.basicBlock2.previousConnection; - const cursorLocation = this.basicBlock4.nextConnection; - - this.navigation.connect(cursorLocation, markedLocation); - - chai.assert.equal( - this.basicBlock.nextConnection.targetBlock(), - this.basicBlock4, - ); - chai.assert.isNull(this.basicBlock3.nextConnection.targetConnection); - }); - - test('Connect cursor with parents', function () { - const markedLocation = this.basicBlock3.previousConnection; - const cursorLocation = this.basicBlock2.nextConnection; - - this.navigation.connect(cursorLocation, markedLocation); - - chai.assert.equal( - this.basicBlock3.previousConnection.targetBlock(), - this.basicBlock2, - ); - }); - - test('Try to connect input that is descendant of output', function () { - const markedLocation = this.inlineBlock2.inputList[0].connection; - const cursorLocation = this.inlineBlock1.outputConnection; - - this.navigation.connect(cursorLocation, markedLocation); - - chai.assert.isNull(this.inlineBlock2.outputConnection.targetBlock()); - chai.assert.equal( - this.inlineBlock1.outputConnection.targetBlock(), - this.inlineBlock2, - ); - }); - test.skip('Do not connect a shadow block', function () { - // TODO(https://github.com/google/blockly-samples/issues/538): Update - // tests after this bug is fixed. - this.inlineBlock2.setShadow(true); - - const markedLocation = this.inlineBlock2.outputConnection; - const cursorLocation = this.inlineBlock3.inputList[0].connection; - const didConnect = this.navigation.connect( - cursorLocation, - markedLocation, - ); - chai.assert.isFalse(didConnect); - chai.assert.isNull(this.inlineBlock2.outputConnection.targetBlock()); - chai.assert.equal( - this.inlineBlock1.outputConnection.targetBlock(), - this.inlineBlock2, - ); - }); - }); - - suite('Test cursor move on block delete', function () { - setup(function () { - Blockly.defineBlocksWithJsonArray([ - { - type: 'basic_block', - message0: '', - previousStatement: null, - nextStatement: null, - }, - ]); - this.workspace = createNavigationWorkspace(this.navigation, true); - - this.basicBlockA = this.workspace.newBlock('basic_block'); - this.basicBlockB = this.workspace.newBlock('basic_block'); - }); - - teardown(function () { - this.navigation.removeWorkspace(this.workspace); - this.workspace.dispose(); - sinon.restore(); - delete Blockly.Blocks['basic_block']; - }); - - test('Delete block - has parent ', function () { - this.basicBlockA.nextConnection.connect( - this.basicBlockB.previousConnection, - ); - const astNode = Blockly.ASTNode.createBlockNode(this.basicBlockB); - // Set the cursor to be on the child block - this.workspace.getCursor().setCurNode(astNode); - // Remove the child block - const mockEvent = createKeyDownEvent(Blockly.utils.KeyCodes.DELETE, ''); - - // Actions that happen when a block is deleted were causing problems. - // Since this is not what we are trying to test and does not effect the - // feature, disable events. - Blockly.Events.disable(); - Blockly.ShortcutRegistry.registry.onKeyDown(this.workspace, mockEvent); - Blockly.Events.enable(); - - chai.assert.equal( - this.workspace.getCursor().getCurNode().getType(), - Blockly.ASTNode.types.NEXT, - ); - }); - - test('Delete block - no parent ', function () { - const astNode = Blockly.ASTNode.createBlockNode(this.basicBlockB); - this.workspace.getCursor().setCurNode(astNode); - - const mockEvent = createKeyDownEvent(Blockly.utils.KeyCodes.DELETE, ''); - - // Actions that happen when a block is deleted were causing problems. - // Since this is not what we are trying to test and does not effect the - // feature, disable events. - Blockly.Events.disable(); - Blockly.ShortcutRegistry.registry.onKeyDown(this.workspace, mockEvent); - Blockly.Events.enable(); - - chai.assert.equal( - this.workspace.getCursor().getCurNode().getType(), - Blockly.ASTNode.types.WORKSPACE, - ); - }); - - test('Delete parent block', function () { - this.basicBlockA.nextConnection.connect( - this.basicBlockB.previousConnection, - ); - const astNode = Blockly.ASTNode.createBlockNode(this.basicBlockB); - const mockDeleteBlockEvent = { - blockId: this.basicBlockA, - ids: [this.basicBlockA.id, this.basicBlockB.id], - }; - // Set the cursor to be on the child block - this.workspace.getCursor().setCurNode(astNode); - // Remove the parent block - this.navigation.handleBlockDeleteByDrag( - this.workspace, - mockDeleteBlockEvent, - ); - chai.assert.equal( - this.workspace.getCursor().getCurNode().getType(), - Blockly.ASTNode.types.WORKSPACE, - ); - }); - - test('Delete top block in stack', function () { - this.basicBlockA.nextConnection.connect( - this.basicBlockB.previousConnection, - ); - const astNode = Blockly.ASTNode.createStackNode(this.basicBlockA); - const mockDeleteBlockEvent = { - blockId: this.basicBlockA.id, - ids: [this.basicBlockA.id, this.basicBlockB.id], - }; - // Set the cursor to be on the stack - this.workspace.getCursor().setCurNode(astNode); - // Remove the top block in the stack - this.navigation.handleBlockDeleteByDrag( - this.workspace, - mockDeleteBlockEvent, - ); - chai.assert.equal( - this.workspace.getCursor().getCurNode().getType(), - Blockly.ASTNode.types.WORKSPACE, - ); - }); - }); - - suite('Test workspace listener', function () { - setup(function () { - Blockly.defineBlocksWithJsonArray([ - { - type: 'basic_block', - message0: '%1', - args0: [ - { - type: 'field_input', - name: 'TEXTFIELD', - text: 'test', - }, - ], - previousStatement: null, - nextStatement: null, - }, - ]); - this.workspace = createNavigationWorkspace(this.navigation, true); - this.workspaceChangeListener = this.navigation.wsChangeWrapper; - this.basicBlockA = this.workspace.newBlock('basic_block'); - }); - - teardown(function () { - this.navigation.removeWorkspace(this.workspace); - this.workspace.dispose(); - delete Blockly.Blocks['basic_block']; - sinon.restore(); - }); - - test('Handle block mutation', function () { - const e = { - type: Blockly.Events.BLOCK_CHANGE, - element: 'mutation', - blockId: this.basicBlockA.id, - workspaceId: this.workspace.id, - }; - const cursor = this.workspace.getCursor(); - const nextNode = Blockly.ASTNode.createConnectionNode( - this.basicBlockA.nextConnection, - ); - cursor.setCurNode(nextNode); - this.workspaceChangeListener(e); - chai.assert.equal( - cursor.getCurNode().getType(), - Blockly.ASTNode.types.BLOCK, - ); - }); - test('Handle workspace click', function () { - const e = { - type: Blockly.Events.CLICK, - workspaceId: this.workspace.id, - }; - this.navigation.focusFlyout(this.workspace); - chai.assert.equal( - this.navigation.getState(this.workspace), - Constants.STATE.FLYOUT, - ); - - this.workspaceChangeListener(e); - - chai.assert.equal( - this.navigation.getState(this.workspace), - Constants.STATE.WORKSPACE, - ); - }); - test('Focus toolbox if category clicked', function () { - const e = { - type: Blockly.Events.TOOLBOX_ITEM_SELECT, - workspaceId: this.workspace.id, - newItem: true, - }; - const toolboxFocusStub = sinon.spy(this.navigation, 'focusToolbox'); - - this.navigation.focusWorkspace(this.workspace); - chai.assert.equal( - this.navigation.getState(this.workspace), - Constants.STATE.WORKSPACE, - ); - - this.workspaceChangeListener(e); - - sinon.assert.calledOnce(toolboxFocusStub); - chai.assert.equal( - this.navigation.getState(this.workspace), - Constants.STATE.TOOLBOX, - ); - }); - test('Focus workspace if toolbox is unselected', function () { - const e = { - type: Blockly.Events.TOOLBOX_ITEM_SELECT, - workspaceId: this.workspace.id, - newItem: false, - }; - const resetFlyoutStub = sinon.spy(this.navigation, 'resetFlyout'); - this.navigation.setState(this.workspace, Constants.STATE.TOOLBOX); - - this.workspaceChangeListener(e); - - sinon.assert.calledOnce(resetFlyoutStub); - chai.assert.equal( - this.navigation.getState(this.workspace), - Constants.STATE.WORKSPACE, - ); - }); - test('Focus workspace when block created on workspace', function () { - const e = { - type: Blockly.Events.BLOCK_CREATE, - workspaceId: this.workspace.id, - }; - const resetFlyoutStub = sinon.spy(this.navigation, 'resetFlyout'); - // Only works when someone is in the flyout. - this.navigation.setState(this.workspace, Constants.STATE.FLYOUT); - - this.workspaceChangeListener(e); - - sinon.assert.calledOnce(resetFlyoutStub); - chai.assert.equal( - this.navigation.getState(this.workspace), - Constants.STATE.WORKSPACE, - ); - }); - }); - - suite('Test simple flyout listener', function () { - setup(function () { - Blockly.defineBlocksWithJsonArray([ - { - type: 'basic_block', - message0: '%1', - args0: [ - { - type: 'field_input', - name: 'TEXTFIELD', - text: 'test', - }, - ], - }, - ]); - this.workspace = createNavigationWorkspace(this.navigation, true); - this.flyoutChangeListener = this.navigation.flyoutChangeWrapper; - this.basicBlockA = this.workspace.newBlock('basic_block'); - - this.navigation.focusToolbox(this.workspace); - this.workspace.getFlyout().autoClose = false; - }); - - teardown(function () { - delete Blockly.Blocks['basic_block']; - this.navigation.removeWorkspace(this.workspace); - this.workspace.dispose(); - sinon.restore(); - }); - test('Handle block click in flyout - click event', function () { - const flyout = this.workspace.getFlyout(); - const flyoutWorkspace = flyout.getWorkspace(); - const firstFlyoutBlock = flyoutWorkspace.getTopBlocks()[0]; - const e = { - type: Blockly.Events.CLICK, - workspaceId: flyoutWorkspace.id, - targetType: 'block', - blockId: firstFlyoutBlock.id, - }; - const flyoutCursor = flyoutWorkspace.getCursor(); - this.navigation.focusWorkspace(this.workspace); - - this.flyoutChangeListener(e); - - chai.assert.equal( - flyoutCursor.getCurNode().getType(), - Blockly.ASTNode.types.STACK, - ); - chai.assert.equal( - this.navigation.getState(this.workspace), - Constants.STATE.FLYOUT, - ); - }); - test('Handle block click in flyout - select event', function () { - const flyout = this.workspace.getFlyout(); - const flyoutWorkspace = flyout.getWorkspace(); - const firstFlyoutBlock = flyoutWorkspace.getTopBlocks()[0]; - const e = { - type: Blockly.Events.SELECTED, - workspaceId: flyoutWorkspace.id, - newElementId: firstFlyoutBlock.id, - }; - const flyoutCursor = flyoutWorkspace.getCursor(); - this.navigation.focusWorkspace(this.workspace); - - this.flyoutChangeListener(e); - - chai.assert.equal( - flyoutCursor.getCurNode().getType(), - Blockly.ASTNode.types.STACK, - ); - chai.assert.equal( - this.navigation.getState(this.workspace), - Constants.STATE.FLYOUT, - ); - }); - }); - - suite('Test clean up methods', function () { - setup(function () { - this.workspace = createNavigationWorkspace(this.navigation, true); - }); - test('All listeners and markers removed', function () { - const numListeners = this.workspace.listeners.length; - const markerName = this.navigation.MARKER_NAME; - this.navigation.removeWorkspace(this.workspace); - chai.assert.equal(this.workspace.listeners.length, numListeners - 1); - - const marker = this.workspace.getMarkerManager().getMarker(markerName); - chai.assert.isNull(marker); - }); - test('Keyboard accessibility mode can not be enabled', function () { - this.navigation.removeWorkspace(this.workspace); - this.navigation.enableKeyboardAccessibility(this.workspace); - chai.assert.isFalse(this.workspace.keyboardAccessibilityMode); - }); - test('Dispose', function () { - const numListeners = this.workspace.listeners.length; - const flyout = this.workspace.getFlyout(); - const numFlyoutListeners = flyout.getWorkspace().listeners.length; - this.navigation.dispose(); - chai.assert.equal(this.workspace.listeners.length, numListeners - 1); - chai.assert.equal( - flyout.getWorkspace().listeners.length, - numFlyoutListeners - 1, - ); - }); - }); -}); diff --git a/plugins/keyboard-navigation/test/shortcuts_test.mocha.js b/plugins/keyboard-navigation/test/shortcuts_test.mocha.js deleted file mode 100644 index 4e1909521..000000000 --- a/plugins/keyboard-navigation/test/shortcuts_test.mocha.js +++ /dev/null @@ -1,531 +0,0 @@ -/** - * @license - * Copyright 2021 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -const sinon = require('sinon'); -const chai = require('chai'); - -const Blockly = require('blockly'); - -const {NavigationController} = require('../src/index'); -const { - createNavigationWorkspace, - createKeyDownEvent, -} = require('./test_helper'); - -suite('Shortcut Tests', function () { - setup(function () { - this.jsdomCleanup = require('jsdom-global')( - '
', - ); - // We are running these tests in node even thought they require a rendered - // workspace, which doesn't exactly work. The rendering system expects - // cancelAnimationFrame to be defined so we need to define it. - window.cancelAnimationFrame = function () {}; - - Blockly.utils.dom.getFastTextWidthWithSizeString = function () { - return 10; - }; - Blockly.defineBlocksWithJsonArray([ - { - type: 'basic_block', - message0: '', - previousStatement: null, - nextStatement: null, - }, - ]); - this.controller = new NavigationController(); - this.controller.init(); - this.navigation = this.controller.navigation; - this.workspace = createNavigationWorkspace(this.navigation, true); - this.controller.addWorkspace(this.workspace); - this.basicBlock = this.workspace.newBlock('basic_block'); - }); - - teardown(function () { - window.cancelAnimationFrame = undefined; - this.controller.dispose(); - this.workspace.dispose(); - this.jsdomCleanup(); - delete Blockly.Blocks['basic_block']; - }); - - suite('Deleting blocks', function () { - setup(function () { - const blockNode = Blockly.ASTNode.createBlockNode(this.basicBlock); - this.workspace.getCursor().setCurNode(blockNode); - }); - - teardown(function () { - sinon.restore(); - }); - - const testCases = [ - { - name: 'Delete', - deleteEvent: createKeyDownEvent( - Blockly.utils.KeyCodes.DELETE, - 'NotAField', - ), - }, - { - name: 'Backspace', - deleteEvent: createKeyDownEvent( - Blockly.utils.KeyCodes.BACKSPACE, - 'NotAField', - ), - }, - ]; - - suite('delete keybinds trigger deletion', function () { - testCases.forEach(function (testCase) { - test(testCase.name, function () { - Blockly.ShortcutRegistry.registry.onKeyDown( - this.workspace, - testCase.deleteEvent, - ); - chai.assert.equal( - this.workspace.getTopBlocks().length, - 0, - 'Expected the block to be deleted.', - ); - }); - }); - }); - - suite( - 'delete keybinds do not trigger deletion if workspace is readonly', - function () { - testCases.forEach(function (testCase) { - test(testCase.name, function () { - this.workspace.options.readOnly = true; - Blockly.ShortcutRegistry.registry.onKeyDown( - this.workspace, - testCase.deleteEvent, - ); - chai.assert.equal( - this.workspace.getTopBlocks().length, - 1, - 'Expected the block to not be deleted.', - ); - }); - }); - }, - ); - }); - - suite('Copy and paste', function () { - teardown(function () { - sinon.restore(); - }); - const testCases = [ - { - name: 'Control', - copyEvent: createKeyDownEvent(Blockly.utils.KeyCodes.C, 'NotAField', [ - Blockly.utils.KeyCodes.CTRL, - ]), - pasteEvent: createKeyDownEvent(Blockly.utils.KeyCodes.V, 'NotAField', [ - Blockly.utils.KeyCodes.CTRL, - ]), - }, - { - name: 'Meta', - copyEvent: createKeyDownEvent(Blockly.utils.KeyCodes.C, 'NotAField', [ - Blockly.utils.KeyCodes.META, - ]), - pasteEvent: createKeyDownEvent(Blockly.utils.KeyCodes.V, 'NotAField', [ - Blockly.utils.KeyCodes.META, - ]), - }, - { - name: 'Alt', - copyEvent: createKeyDownEvent(Blockly.utils.KeyCodes.C, 'NotAField', [ - Blockly.utils.KeyCodes.ALT, - ]), - pasteEvent: createKeyDownEvent(Blockly.utils.KeyCodes.V, 'NotAField', [ - Blockly.utils.KeyCodes.ALT, - ]), - }, - ]; - - suite('copy and paste keybinds duplicate blocks', function () { - setup(function () { - const blockNode = Blockly.ASTNode.createBlockNode(this.basicBlock); - this.workspace.getCursor().setCurNode(blockNode); - }); - - testCases.forEach(function (testCase) { - test(testCase.name, function () { - Blockly.ShortcutRegistry.registry.onKeyDown( - this.workspace, - testCase.copyEvent, - ); - Blockly.ShortcutRegistry.registry.onKeyDown( - this.workspace, - testCase.pasteEvent, - ); - chai.assert.equal( - this.workspace.getTopBlocks().length, - 2, - 'Expected the block to be duplicated.', - ); - }); - }); - }); - - suite( - 'copy and paste does nothing if the cursor is not on a block', - function () { - setup(function () { - const workspaceNode = Blockly.ASTNode.createWorkspaceNode( - this.workspace, - new Blockly.utils.Coordinate(100, 100), - ); - this.workspace.getCursor().setCurNode(workspaceNode); - }); - testCases.forEach(function (testCase) { - test(testCase.name, function () { - Blockly.ShortcutRegistry.registry.onKeyDown( - this.workspace, - testCase.copyEvent, - ); - Blockly.ShortcutRegistry.registry.onKeyDown( - this.workspace, - testCase.pasteEvent, - ); - chai.assert.equal( - this.workspace.getTopBlocks().length, - 1, - 'Expected the block to not be duplicated.', - ); - }); - }); - }, - ); - - suite( - 'copy and paste do nothing if the cursor is on a shadow block', - function () { - setup(function () { - this.basicBlock.setShadow(true); - const blockNode = Blockly.ASTNode.createBlockNode(this.basicBlock); - this.workspace.getCursor().setCurNode(blockNode); - }); - testCases.forEach(function (testCase) { - test(testCase.name, function () { - Blockly.ShortcutRegistry.registry.onKeyDown( - this.workspace, - testCase.copyEvent, - ); - Blockly.ShortcutRegistry.registry.onKeyDown( - this.workspace, - testCase.pasteEvent, - ); - chai.assert.equal( - this.workspace.getTopBlocks().length, - 1, - 'Expected the block to not be duplicated.', - ); - }); - }); - }, - ); - - suite( - 'copy and paste do nothing if the workspace is readonly', - function () { - setup(function () { - this.workspace.options.readonly = true; - }); - testCases.forEach(function (testCase) { - test(testCase.name, function () { - Blockly.ShortcutRegistry.registry.onKeyDown( - this.workspace, - testCase.copyEvent, - ); - Blockly.ShortcutRegistry.registry.onKeyDown( - this.workspace, - testCase.pasteEvent, - ); - chai.assert.equal( - this.workspace.getTopBlocks().length, - 1, - 'Expected the block to not be duplicated.', - ); - }); - }); - }, - ); - - suite('copy and paste do nothing if a gesture is in progress', function () { - setup(function () { - sinon.stub(Blockly.Gesture, 'inProgress').returns(true); - }); - testCases.forEach(function (testCase) { - test(testCase.name, function () { - Blockly.ShortcutRegistry.registry.onKeyDown( - this.workspace, - testCase.copyEvent, - ); - Blockly.ShortcutRegistry.registry.onKeyDown( - this.workspace, - testCase.pasteEvent, - ); - chai.assert.equal( - this.workspace.getTopBlocks().length, - 1, - 'Expected the block to not be duplicated.', - ); - }); - }); - }); - - suite( - 'copy and paste do nothing if the block is not deletable', - function () { - setup(function () { - this.basicBlock.setDeletable(false); - }); - testCases.forEach(function (testCase) { - test(testCase.name, function () { - Blockly.ShortcutRegistry.registry.onKeyDown( - this.workspace, - testCase.copyEvent, - ); - Blockly.ShortcutRegistry.registry.onKeyDown( - this.workspace, - testCase.pasteEvent, - ); - chai.assert.equal( - this.workspace.getTopBlocks().length, - 1, - 'Expected the block to not be duplicated.', - ); - }); - }); - }, - ); - }); - - suite('Cut and paste', function () { - teardown(function () { - sinon.restore(); - }); - const testCases = [ - { - name: 'Control', - cutEvent: createKeyDownEvent(Blockly.utils.KeyCodes.X, 'NotAField', [ - Blockly.utils.KeyCodes.CTRL, - ]), - pasteEvent: createKeyDownEvent(Blockly.utils.KeyCodes.V, 'NotAField', [ - Blockly.utils.KeyCodes.CTRL, - ]), - }, - { - name: 'Meta', - cutEvent: createKeyDownEvent(Blockly.utils.KeyCodes.X, 'NotAField', [ - Blockly.utils.KeyCodes.META, - ]), - pasteEvent: createKeyDownEvent(Blockly.utils.KeyCodes.V, 'NotAField', [ - Blockly.utils.KeyCodes.META, - ]), - }, - { - name: 'Alt', - cutEvent: createKeyDownEvent(Blockly.utils.KeyCodes.X, 'NotAField', [ - Blockly.utils.KeyCodes.ALT, - ]), - pasteEvent: createKeyDownEvent(Blockly.utils.KeyCodes.V, 'NotAField', [ - Blockly.utils.KeyCodes.ALT, - ]), - }, - ]; - - suite('cut and paste keybinds duplicate blocks', function () { - setup(function () { - const blockNode = Blockly.ASTNode.createBlockNode(this.basicBlock); - this.workspace.getCursor().setCurNode(blockNode); - }); - - testCases.forEach(function (testCase) { - test(testCase.name, function () { - Blockly.ShortcutRegistry.registry.onKeyDown( - this.workspace, - testCase.cutEvent, - ); - chai.assert.equal( - this.workspace.getTopBlocks().length, - 0, - 'Expected the block to be deleted.', - ); - Blockly.ShortcutRegistry.registry.onKeyDown( - this.workspace, - testCase.pasteEvent, - ); - chai.assert.equal( - this.workspace.getTopBlocks().length, - 1, - 'Expected the block to be duplicated.', - ); - }); - }); - }); - - suite( - 'cut and paste does nothing if the cursor is not on a block', - function () { - setup(function () { - const workspaceNode = Blockly.ASTNode.createWorkspaceNode( - this.workspace, - new Blockly.utils.Coordinate(100, 100), - ); - this.workspace.getCursor().setCurNode(workspaceNode); - }); - testCases.forEach(function (testCase) { - test(testCase.name, function () { - Blockly.ShortcutRegistry.registry.onKeyDown( - this.workspace, - testCase.cutEvent, - ); - chai.assert.equal( - this.workspace.getTopBlocks().length, - 1, - 'Expected the block to not be deleted.', - ); - Blockly.ShortcutRegistry.registry.onKeyDown( - this.workspace, - testCase.pasteEvent, - ); - chai.assert.equal( - this.workspace.getTopBlocks().length, - 1, - 'Expected the block to not be duplicated.', - ); - }); - }); - }, - ); - - suite( - 'cut and paste do nothing if the cursor is on a shadow block', - function () { - setup(function () { - this.basicBlock.setShadow(true); - const blockNode = Blockly.ASTNode.createBlockNode(this.basicBlock); - this.workspace.getCursor().setCurNode(blockNode); - }); - testCases.forEach(function (testCase) { - test(testCase.name, function () { - Blockly.ShortcutRegistry.registry.onKeyDown( - this.workspace, - testCase.cutEvent, - ); - chai.assert.equal( - this.workspace.getTopBlocks().length, - 1, - 'Expected the block to not be deleted.', - ); - Blockly.ShortcutRegistry.registry.onKeyDown( - this.workspace, - testCase.pasteEvent, - ); - chai.assert.equal( - this.workspace.getTopBlocks().length, - 1, - 'Expected the block to not be duplicated.', - ); - }); - }); - }, - ); - - suite('cut and paste do nothing if the workspace is readonly', function () { - setup(function () { - this.workspace.options.readonly = true; - }); - testCases.forEach(function (testCase) { - test(testCase.name, function () { - Blockly.ShortcutRegistry.registry.onKeyDown( - this.workspace, - testCase.cutEvent, - ); - chai.assert.equal( - this.workspace.getTopBlocks().length, - 1, - 'Expected the block to not be deleted.', - ); - Blockly.ShortcutRegistry.registry.onKeyDown( - this.workspace, - testCase.pasteEvent, - ); - chai.assert.equal( - this.workspace.getTopBlocks().length, - 1, - 'Expected the block to not be duplicated.', - ); - }); - }); - }); - - suite('cut and paste do nothing if a gesture is in progress', function () { - setup(function () { - sinon.stub(Blockly.Gesture, 'inProgress').returns(true); - }); - testCases.forEach(function (testCase) { - test(testCase.name, function () { - Blockly.ShortcutRegistry.registry.onKeyDown( - this.workspace, - testCase.cutEvent, - ); - chai.assert.equal( - this.workspace.getTopBlocks().length, - 1, - 'Expected the block to not be deleted.', - ); - Blockly.ShortcutRegistry.registry.onKeyDown( - this.workspace, - testCase.pasteEvent, - ); - chai.assert.equal( - this.workspace.getTopBlocks().length, - 1, - 'Expected the block to not be duplicated.', - ); - }); - }); - }); - - suite( - 'cut and paste do nothing if the block is not deletable', - function () { - setup(function () { - this.basicBlock.setDeletable(false); - }); - testCases.forEach(function (testCase) { - test(testCase.name, function () { - Blockly.ShortcutRegistry.registry.onKeyDown( - this.workspace, - testCase.cutEvent, - ); - chai.assert.equal( - this.workspace.getTopBlocks().length, - 1, - 'Expected the block to not be deleted.', - ); - Blockly.ShortcutRegistry.registry.onKeyDown( - this.workspace, - testCase.pasteEvent, - ); - chai.assert.equal( - this.workspace.getTopBlocks().length, - 1, - 'Expected the block to not be duplicated.', - ); - }); - }); - }, - ); - }); -}); diff --git a/plugins/keyboard-navigation/test/test_helper.js b/plugins/keyboard-navigation/test/test_helper.js deleted file mode 100644 index 99abcb5fb..000000000 --- a/plugins/keyboard-navigation/test/test_helper.js +++ /dev/null @@ -1,89 +0,0 @@ -/** - * @license - * Copyright 2021 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -const {Constants} = require('../src/index'); -const {Navigation} = require('../src/index'); -const Blockly = require('blockly/core'); - -/** - * Creates a workspace for testing keyboard navigation. - * @param {Navigation} navigation Object holding navigation classes. - * @param {boolean} enableKeyboardNav True to enable keyboard navigation, false - * otherwise. - * @param {boolean} readOnly True for a read only workspace, false otherwise. - * @returns {Blockly.WorkspaceSvg} The created workspace. - */ -export function createNavigationWorkspace( - navigation, - enableKeyboardNav, - readOnly, -) { - const workspace = Blockly.inject('blocklyDiv', { - toolbox: ` - - `, - readOnly: readOnly, - }); - if (enableKeyboardNav) { - navigation.addWorkspace(workspace); - navigation.enableKeyboardAccessibility(workspace); - navigation.setState(workspace, Constants.STATE.WORKSPACE); - } - return workspace; -} - -/** - * Creates a key down event used for testing. - * @param {number} keyCode The keycode for the event. Use Blockly.utils.KeyCodes - * enum. - * @param {string} type The type of the target. This only matters for the - * Blockly.utils.isTargetInput method. - * @param {?Array} modifiers A list of modifiers. Use - * Blockly.utils.KeyCodes enum. - * @returns {Object} The mocked keydown - * event. - */ -export function createKeyDownEvent(keyCode, type, modifiers) { - const event = { - keyCode: keyCode, - target: {type: type}, - getModifierState: function (name) { - if (name == 'Shift' && this.shiftKey) { - return true; - } else if (name == 'Control' && this.ctrlKey) { - return true; - } else if (name == 'Meta' && this.metaKey) { - return true; - } else if (name == 'Alt' && this.altKey) { - return true; - } - return false; - }, - preventDefault: function () {}, - }; - if (modifiers && modifiers.length) { - event.altKey = modifiers.includes(Blockly.utils.KeyCodes.ALT); - event.ctrlKey = modifiers.includes(Blockly.utils.KeyCodes.CTRL); - event.metaKey = modifiers.includes(Blockly.utils.KeyCodes.META); - event.shiftKey = modifiers.includes(Blockly.utils.KeyCodes.SHIFT); - } - return event; -} diff --git a/plugins/keyboard-navigation/test/toolbox.js b/plugins/keyboard-navigation/test/toolbox.js deleted file mode 100644 index 95dd4ff7b..000000000 --- a/plugins/keyboard-navigation/test/toolbox.js +++ /dev/null @@ -1,218 +0,0 @@ -/** - * @license - * Copyright 2024 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -/** - * @fileoverview A custom toolbox for the plugin test. - */ - -export const toolbox = { - kind: 'categoryToolbox', - contents: [ - { - kind: 'category', - name: 'Logic', - categorystyle: 'logic_category', - contents: [ - { - type: 'controls_if', - kind: 'block', - }, - { - type: 'logic_compare', - kind: 'block', - fields: { - OP: 'EQ', - }, - }, - { - type: 'logic_operation', - kind: 'block', - fields: { - OP: 'AND', - }, - }, - ], - }, - { - kind: 'category', - name: 'Loops', - categorystyle: 'loop_category', - contents: [ - { - type: 'controls_repeat_ext', - kind: 'block', - inputs: { - TIMES: { - shadow: { - type: 'math_number', - fields: { - NUM: 10, - }, - }, - }, - }, - }, - { - type: 'controls_repeat', - kind: 'block', - enabled: false, - fields: { - TIMES: 10, - }, - }, - { - type: 'controls_whileUntil', - kind: 'block', - fields: { - MODE: 'WHILE', - }, - }, - { - type: 'controls_for', - kind: 'block', - fields: { - VAR: { - name: 'i', - }, - }, - inputs: { - FROM: { - shadow: { - type: 'math_number', - fields: { - NUM: 1, - }, - }, - }, - TO: { - shadow: { - type: 'math_number', - fields: { - NUM: 10, - }, - }, - }, - BY: { - shadow: { - type: 'math_number', - fields: { - NUM: 1, - }, - }, - }, - }, - }, - { - type: 'controls_forEach', - kind: 'block', - fields: { - VAR: { - name: 'j', - }, - }, - }, - { - type: 'controls_flow_statements', - kind: 'block', - enabled: false, - fields: { - FLOW: 'BREAK', - }, - }, - ], - }, - { - kind: 'sep', - }, - { - kind: 'category', - name: 'Variables', - custom: 'VARIABLE', - categorystyle: 'variable_category', - }, - { - kind: 'category', - name: 'Buttons and Blocks', - categorystyle: 'loop_category', - contents: [ - { - type: 'controls_repeat', - kind: 'block', - fields: { - TIMES: 10, - }, - }, - { - kind: 'BUTTON', - text: 'Randomize Button Style', - callbackkey: 'setRandomStyle', - }, - { - kind: 'BUTTON', - text: 'Randomize Button Style', - callbackkey: 'setRandomStyle', - }, - { - type: 'controls_repeat', - kind: 'block', - fields: { - TIMES: 10, - }, - }, - { - kind: 'BUTTON', - text: 'Randomize Button Style', - callbackkey: 'setRandomStyle', - }, - ], - }, - { - kind: 'sep', - }, - { - kind: 'category', - name: 'Nested Categories', - contents: [ - { - kind: 'category', - name: 'sub-category 1', - contents: [ - { - kind: 'BUTTON', - text: 'Randomize Button Style', - callbackkey: 'setRandomStyle', - }, - { - type: 'logic_boolean', - kind: 'block', - fields: { - BOOL: 'TRUE', - }, - }, - ], - }, - { - kind: 'category', - name: 'sub-category 2', - contents: [ - { - type: 'logic_boolean', - kind: 'block', - fields: { - BOOL: 'FALSE', - }, - }, - { - kind: 'BUTTON', - text: 'Randomize Button Style', - callbackkey: 'setRandomStyle', - }, - ], - }, - ], - }, - ], -};