Merge branch 'master' into simpler-matcher-creation
diff --git a/.eslintignore b/.eslintignore
index ba489a4..652cc15 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -3,5 +3,6 @@
 !/packages/*/src/types
 **/*.d.ts
 /coverage
+/docs
 /packages/*/lib
 /web/dist
diff --git a/.eslintrc.js b/.eslintrc.js
index 2dd3ce1..ec1ae7d 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -18,36 +18,35 @@
  * under the License.
  */
 
-const path = require('path');
-
-const babel = require('@babel/core');
-
-// Use the root babel.config.js for module resolution.
-// Relevant issue: tleunen/eslint-import-resolver-babel-module#89
-const babelConfig = babel.loadPartialConfig({ cwd: __dirname });
-const babelModuleResolver = babelConfig.options.plugins.find(
-  (item) => item.file.request === 'module-resolver',
-);
-
 module.exports = {
   root: true,
   extends: ['eslint:recommended', 'plugin:import/recommended', 'prettier'],
   plugins: ['import', 'prettier'],
   rules: {
-    'import/extensions': [
-      'error',
-      'ignorePackages',
-      {
-        ts: 'never',
-      },
-    ],
+    'import/extensions': ['error', 'never'],
     'import/first': 'error',
     'import/newline-after-import': 'error',
+    'import/no-absolute-path': 'error',
     'import/no-default-export': 'error',
-    'import/no-internal-modules': 'error',
-    'import/no-relative-parent-imports': 'error',
-    'import/order': ['error', { 'newlines-between': 'always' }],
+    'import/order': [
+      'error',
+      {
+        alphabetize: {
+          order: 'asc',
+        },
+        groups: [
+          'builtin',
+          'external',
+          'internal',
+          'parent',
+          'sibling',
+          'index',
+        ],
+        'newlines-between': 'never',
+      },
+    ],
     'import/unambiguous': 'error',
+    'no-constant-condition': 'off',
     'prettier/prettier': [
       'error',
       {
@@ -57,8 +56,13 @@
     ],
   },
   settings: {
+    'import/internal-regex': '^@apache-annotator/',
     'import/resolver': {
-      'babel-module': babelModuleResolver.options,
+      'babel-module': {
+        babelOptions: {
+          root: __dirname,
+        },
+      },
     },
   },
   overrides: [
@@ -76,8 +80,11 @@
         es2017: true,
         node: true,
       },
+      globals: {
+        globalThis: 'readonly',
+      },
       parserOptions: {
-        ecmaVersion: 2018,
+        ecmaVersion: 2019,
       },
       plugins: ['node'],
       rules: {
@@ -89,10 +96,6 @@
     },
     {
       files: ['**/*.ts'],
-      env: {
-        es2020: true,
-        'shared-node-browser': true,
-      },
       extends: [
         'plugin:@typescript-eslint/recommended',
         'plugin:@typescript-eslint/recommended-requiring-type-checking',
@@ -101,11 +104,15 @@
       ],
       parserOptions: {
         ecmaVersion: 2020,
-        project: ['./tsconfig.json', './packages/*/tsconfig.json'],
+        project: ['./tsconfig.test.json', './packages/*/tsconfig.json'],
         tsconfigRootDir: __dirname,
+        EXPERIMENTAL_useSourceOfProjectReferenceRedirect: true,
       },
       plugins: ['@typescript-eslint'],
       rules: {
+        '@typescript-eslint/consistent-type-imports': 'error',
+        '@typescript-eslint/no-duplicate-imports': 'error',
+        '@typescript-eslint/no-explicit-any': 'off',
         '@typescript-eslint/no-unused-vars': [
           'error',
           { argsIgnorePattern: '^_' },
@@ -122,35 +129,18 @@
     },
     {
       files: ['packages/*/test/**/*.ts', 'test/**/*.ts'],
-      env: {
-        mocha: true,
-      },
-      globals: {
-        assert: true,
-      },
       rules: {
-        'import/no-internal-modules': [
-          'error',
-          {
-            allow: [path.resolve(__dirname, './packages/*/src/**')],
-          },
-        ],
         'import/no-relative-parent-imports': 'off',
       },
     },
     {
-      files: ['packages/dom/**/*.js'],
-      env: {
-        browser: true,
-      },
-    },
-    {
-      files: ['web/demo/**/*.js'],
+      files: ['web/**/*.js'],
       env: {
         browser: true,
         es2020: true,
       },
       parserOptions: {
+        ecmaVersion: 2020,
         sourceType: 'module',
       },
     },
diff --git a/.gitignore b/.gitignore
index 807d102..f28d081 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,10 +1,15 @@
 *.d.ts
+!/packages/**/@types/**/*.d.ts
 *.d.ts.map
 .nyc_output
 coverage
+docs
 node_modules
 tsconfig.tsbuildinfo
+tsconfig.test.tsbuildinfo
+/packages/*/DISCLAIMER-WIP
 /packages/*/LICENSE
 /packages/*/NOTICE
+/packages/*/README.md
 /packages/*/lib
 /web/dist
diff --git a/.mocharc.js b/.mocharc.js
index f2eac6a..92e13b0 100644
--- a/.mocharc.js
+++ b/.mocharc.js
@@ -21,6 +21,11 @@
 module.exports = {
   extension: ['.ts'],
   ignore: ['node_modules'],
-  require: ['./babel-register.js', 'global-jsdom/lib/register'],
+  require: ['./babel-register.js', 'global-jsdom/register'],
   timeout: 5000,
+  watchFiles: [
+    './test/**/*.ts',
+    './packages/*/src/**/*.ts',
+    './packages/*/test/**/*.ts',
+  ],
 };
diff --git a/.travis.yml b/.travis.yml
index fd27504..bdcc041 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,13 +1,14 @@
-dist: bionic
+dist: focal
 language: node_js
-node_js:
-  - "10"
-  - "12"
-  - "13"
-  - "14"
+node_js: node
 
-# Travis is pretty behind the curve on their Yarn support...
-# https://docs.travis-ci.com/user/languages/javascript-with-nodejs/#Using-a-specific-yarn-version
-before_install:
-  - curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --version 1.5.1
-  - export PATH="$HOME/.yarn/bin:$PATH"
+before_deploy:
+  - git fetch --unshallow
+  - git checkout master
+
+deploy:
+  provider: npm
+  edge: true
+  run_script: publish:ci
+  on:
+    branch: master
diff --git a/Makefile b/Makefile
index 0775627..c193ecf 100644
--- a/Makefile
+++ b/Makefile
@@ -66,6 +66,7 @@
         --config advice.detachedHead=false \
         --config versionsort.suffix=-rc \
         --depth 1 \
+        --no-tags \
         --quiet \
         file://"$(shell git rev-parse --show-toplevel)" \
         apache-annotator-$(annotator_vsn)-incubating
diff --git a/README.md b/README.md
index a7d0698..121a3cd 100644
--- a/README.md
+++ b/README.md
@@ -1,119 +1,27 @@
-# [Apache Annotator](http://annotator.apache.org/) (incubating) [![Build Status](https://travis-ci.org/apache/incubator-annotator.svg?branch=master)](https://travis-ci.org/apache/incubator-annotator)
+# [Apache Annotator](http://annotator.apache.org/) (incubating) [![Build Status](https://travis-ci.com/apache/incubator-annotator.svg?branch=master)](https://travis-ci.com/apache/incubator-annotator)
 
-> Apache Annotator provides annotation enabling code for browsers, servers,
-> and humans.
+Apache Annotator (incubating) provides libraries to enable annotation related
+software, with an initial focus on identification of textual fragments in
+browser environments.
 
-* [`dev@` Mailing List archive](http://mail-archives.apache.org/mod_mbox/incubator-annotator-dev/)
-* [Issue Tracker](https://github.com/apache/incubator-annotator/issues)
-* [Wiki](https://github.com/apache/incubator-annotator/wiki)
+## Installation, usage, API documentation
 
-## Usage
+See documentation on the website: <https://annotator.apache.org/docs/>
 
-We're currently pre-releasing development copies of each library that makes up
-the sum total of Apache Annotator's code. You can grab any of them from our
-[npm organization](https://www.npmjs.com/org/annotator).
+## Getting Involved
 
-```sh
-$ # for example...
-$ npm install --save @annotator/dom
-```
-
-##### Requirements
-
-- [node](https://nodejs.org) ^10 || ^11 || ^12 || >=13.7
-- [yarn](https://www.yarnpkg.com/) ^1.5
-
-
-## Development
-
-##### Requirements
-
-We use [Lerna](https://lernajs.io/) to juggle the various Apache Annotator
-libraries. If you'd like to contribute, you'll need the following:
-
-- [node](https://nodejs.org) ^10 || ^11 || ^12 || >=13.7
-- [yarn](https://www.yarnpkg.com/) ^1.5
-
-##### Setup
-
-```sh
-$ yarn install
-```
-
-##### Test
-
-```sh
-$ yarn test
-```
-
-##### Run localhost demo server
-
-```sh
-$ yarn start
-```
-
-Once the test server has started, you can browse a local demo, and run tests in
-a browser by visiting `http://localhost:8080/`.
-
-## Selectors
-
-Many Annotations refer to part of a resource, rather than all of it, as the Target. We call that part of the resource a Segment (of Interest). A Selector is used to describe how to determine the Segment from within the Source resource.
-
-The [W3C Web Annotation Data Model](https://www.w3.org/TR/annotation-model) outlines a number of different selectors. See table below for full list and status.
-
-| Selector                                                                        | Description                                                                                                                                                                                          | Implementation Status |
-| ------------------------------------------------------------------------------: | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | --------------------: |
-| [Text Quote](https://www.w3.org/TR/annotation-model/#text-quote-selector)       | This Selector describes a range of text, including some of the text immediately before (a prefix) and after (a suffix) it to distinguish between multiple copies of the same sequence of characters. | Yes                   |
-| [CSS](https://www.w3.org/TR/annotation-model/#css-selector)                     | CSS Selectors allow for a wide variety of well supported ways to describe the path to an element in a web page.                                                                                      | Yes                   |
-| [Text Position](https://www.w3.org/TR/annotation-model/#text-position-selector) | This Selector describes a range of text by recording the start and end positions of the selection in the stream.                                                                                     | No                    |
-| [Fragment](https://www.w3.org/TR/annotation-model/#fragment-selector)           | Uses the fragment part of an IRI defined by the representation's media type.                                                                                                                         | No                    |
-| [XPath](https://www.w3.org/TR/annotation-model/#xpath-selector)                 | Implements an XPath based selection.                                                                                                                                                                 | No                    |
-| [Data Postion](https://www.w3.org/TR/annotation-model/#data-position-selector)  | Similar to the Text Position Selector, the Data Position Selector uses the same properties but works at the byte in bitstream level rather than the character in text level.                         | No                    |
-| [SVG](https://www.w3.org/TR/annotation-model/#svg-selector)                     | An SvgSelector defines an area through the use of the Scalable Vector Graphics standard.                                                                                                             | No                    |
-| [Range](https://www.w3.org/TR/annotation-model/#range-selector)                 | A Range Selector can be used to identify the beginning and the end of the selection by using other Selectors.                                                                                        | Yes                   |
-| [Refinement](https://www.w3.org/TR/annotation-model/#refinement-of-selection)   | Select a part of a selection, rather than as a selection of the complete resource.                                                                                                                   |                       |
-
-## Web Annotation Data Model Validation
-
-If you have any Web Annotation Data Model JSON documents, you can validate them
-using the `validate` script:
-
-```sh
-$ yarn validate --url https://raw.githubusercontent.com/w3c/web-annotation-tests/master/tools/samples/correct/anno1.json
-```
-
-With the `--url` option you can pass in a URL or a local path to a JSON file.
-
-##### Examples
-
-Valid:
-
-`https://raw.githubusercontent.com/w3c/web-annotation-tests/master/tools/samples/correct/anno1.json`
-
-Invalid:
-
-`https://raw.githubusercontent.com/w3c/web-annotation-tests/master/tools/samples/incorrect/anno1.json`
-
-[(More)](https://github.com/w3c/web-annotation-tests/tree/master/tools/samples)
+* Join the [mailing list](http://mail-archives.apache.org/mod_mbox/incubator-annotator-dev/). Send an email to
+  dev-subscribe@apache-annotator.apache.org to subscribe.
+* Browse the [issue tracker](https://github.com/apache/incubator-annotator/issues) and file new issues if you encounter problems.
+* Read or contribute to the [wiki](https://github.com/apache/incubator-annotator/wiki).
 
 # License
 
 Apache License 2.0
 
-#### Validate Licensing
-
-[Apache Rat (Release Audit Tool)](https://creadur.apache.org/rat/) is a
-preferred code license checking tool used by [the ASF](https://apache.org/).
-The included `.ratignore` file contains a list of files to exclude from scans.
-
-To check for included licenses, run the following and view the output report:
-```sh
-java -jar ~/bin/apache-rat-0.13/apache-rat-0.13.jar -E .ratignore -d . > rat_report.txt
-```
-
 # Disclaimer
 
 Apache Annotator is currently undergoing incubation at The Apache Software
 Foundation.
 
-See the accompanying [DISCLAIMER](./DISCLAIMER) file for details.
+See the accompanying [DISCLAIMER](./DISCLAIMER-WIP) file for details.
diff --git a/babel-register.js b/babel-register.js
index ec45b5b..6ae0a76 100644
--- a/babel-register.js
+++ b/babel-register.js
@@ -18,4 +18,7 @@
  * under the License.
  */
 
-require('@babel/register')({ extensions: ['.ts'] });
+const { DEFAULT_EXTENSIONS } = require('@babel/core');
+require('@babel/register')({
+  extensions: ['.ts', '.tsx', ...DEFAULT_EXTENSIONS],
+});
diff --git a/babel.config.js b/babel.config.js
index 1dddf6a..6eeba3a 100644
--- a/babel.config.js
+++ b/babel.config.js
@@ -18,11 +18,13 @@
  * under the License.
  */
 
+const path = require('path');
+const { DEFAULT_EXTENSIONS } = require('@babel/core');
+
 module.exports = (api) => {
   const ENV = api.env();
   const DEV = ENV === 'development';
   const TEST = ENV === 'test';
-  const CJS = ENV === 'cjs';
 
   // Options for the @babel/env preset.
   const envOptions = {
@@ -30,9 +32,7 @@
     // Note: This setting may become the default in Babel 8.
     bugfixes: true,
     // Transform module syntax if necessary.
-    modules: CJS || TEST ? 'commonjs' : false,
-    // Set target environment to default browsers.
-    targets: TEST ? { node: 'current' } : 'defaults',
+    modules: TEST ? 'commonjs' : false,
   };
 
   // Options for the @babel/typescript preset.
@@ -44,7 +44,7 @@
   };
 
   const addImportExtensionOptions = {
-    extension: DEV || TEST ? 'ts' : CJS ? 'js' : 'mjs',
+    extension: DEV || TEST ? 'ts' : 'js',
   };
 
   // Options for the module-resolver plugin.
@@ -53,42 +53,35 @@
     alias: {
       ...(DEV || TEST
         ? {
-            '^@annotator/(.+)$': '@annotator/\\1/src/index.ts',
+            '^@apache-annotator/([^/]+)$': ([, name]) =>
+              path.join(__dirname, 'packages', name, '/src/index.ts'),
           }
         : null),
-      // TODO: Remove after babel/babel#8462 ships.
-      '^@babel/runtime-corejs3/core-js/(.+)$':
-        '@babel/runtime-corejs3/core-js/\\1.js',
-      '^@babel/runtime-corejs3/core-js-stable/(.+)$':
-        '@babel/runtime-corejs3/core-js-stable/\\1.js',
-      '^@babel/runtime-corejs3/helpers/(.+)$':
-        '@babel/runtime-corejs3/helpers/\\1.js',
-      '^@babel/runtime-corejs3/regenerator$':
-        '@babel/runtime-corejs3/regenerator/index.js',
-      extensions: ['.js', '.ts'],
     },
+    extensions: ['.ts', '.tsx', ...DEFAULT_EXTENSIONS],
   };
 
   // Options for the @babel/transform-runtime plugin.
   const runtimeOptions = {
     // Use corejs version 3.
-    corejs: { version: 3 },
+    corejs: { version: 3, proposals: true },
     // Use helpers formatted for the target environment.
-    // TODO: Re-enable after babel/babel#8462 ships.
-    // useESModules: !CJS && !TEST,
+    useESModules: !TEST,
   };
 
   return {
     plugins: [
+      '@babel/plugin-proposal-class-properties',
       ['@babel/transform-runtime', runtimeOptions],
-      ...(TEST ? ['istanbul'] : []),
       ['add-import-extension', addImportExtensionOptions],
       ['module-resolver', resolverOptions],
       'preserve-comment-header',
+      ...(TEST ? ['istanbul'] : []),
     ],
     presets: [
       ['@babel/env', envOptions],
       ['@babel/typescript', typescriptOptions],
     ],
+    targets: TEST ? { node: 'current' } : 'defaults',
   };
 };
diff --git a/lerna.json b/lerna.json
index 16b3c1a..72d9de3 100644
--- a/lerna.json
+++ b/lerna.json
@@ -6,9 +6,13 @@
     "packages/*"
   ],
   "command": {
+    "publish": {
+      "preDistTag": "dev",
+      "preid": "dev"
+    },
     "version": {
-      "signGitCommit": true,
-      "signGitTag": true
+      "gitTagVersion": false,
+      "push": false
     }
   }
 }
diff --git a/package.json b/package.json
index d97f19c..6ff42e2 100644
--- a/package.json
+++ b/package.json
@@ -15,16 +15,16 @@
     ]
   },
   "scripts": {
-    "build": "yarn run build:cjs && yarn run build:esm && yarn run build:misc && yarn run build:types",
-    "build:babel": "lerna exec --parallel -- babel -d lib -s inline -x .ts --root-mode upward src",
-    "build:cjs": "cross-env BABEL_ENV=cjs yarn build:babel",
-    "build:esm": "cross-env BABEL_ENV=esm yarn build:babel --out-file-extension .mjs",
-    "build:misc": "lerna exec -- cp ../../LICENSE ../../NOTICE .",
+    "build": "concurrently yarn:build:*",
+    "build:js": "lerna exec --parallel -- babel -d lib -s -x .ts --env-name production --root-mode upward src",
+    "build:misc": "lerna exec --parallel -- cp ../../DISCLAIMER-WIP ../../LICENSE ../../NOTICE ../../README.md .",
     "build:types": "tsc --build",
-    "clean": "tsc --build --clean && lerna exec -- rimraf LICENSE NOTICE lib",
-    "lint": "tsc --build && eslint .",
-    "prepare": "yarn run build:types && lerna run prepare",
+    "clean": "tsc --build --clean && lerna exec -- rimraf DISCLAIMER-WIP LICENSE NOTICE README.md lib && rimraf docs",
+    "docs": "tsc --build && typedoc",
+    "lint": "eslint .",
     "prepublishOnly": "yarn run build",
+    "publish": "lerna publish",
+    "publish:ci": "yarn run publish --canary --exact --force-publish '*' --no-verify-access --yes minor",
     "start": "yarn run web:server",
     "test": "cross-env BABEL_ENV=test nyc mocha packages/**/*.test.ts",
     "validate": "cross-env BABEL_ENV=test mocha test/**/*.test.ts",
@@ -32,18 +32,19 @@
     "web:server": "webpack-dev-server --config=web/webpack.config.js --hot --mode development"
   },
   "devDependencies": {
-    "@babel/cli": "^7.10.1",
-    "@babel/core": "^7.10.1",
-    "@babel/plugin-transform-runtime": "^7.10.1",
-    "@babel/preset-env": "^7.10.1",
-    "@babel/preset-typescript": "^7.10.1",
-    "@babel/register": "^7.10.1",
+    "@babel/cli": "^7.13.14",
+    "@babel/core": "^7.13.14",
+    "@babel/plugin-proposal-class-properties": "^7.13.0",
+    "@babel/plugin-transform-runtime": "^7.13.10",
+    "@babel/preset-env": "^7.13.12",
+    "@babel/preset-typescript": "^7.13.0",
+    "@babel/register": "^7.13.14",
     "@types/chai": "^4.2.11",
     "@types/mocha": "^7.0.2",
     "@types/node-fetch": "^2.5.7",
     "@types/resolve": "^1.17.0",
-    "@typescript-eslint/eslint-plugin": "^3.7.0",
-    "@typescript-eslint/parser": "^3.7.0",
+    "@typescript-eslint/eslint-plugin": "^4.20.0",
+    "@typescript-eslint/parser": "^4.20.0",
     "ajv": "^6.11.0",
     "babel-loader": "^8.0.5",
     "babel-plugin-add-import-extension": "^1.4.1",
@@ -51,7 +52,7 @@
     "babel-plugin-module-resolver": "^4.0.0",
     "babel-plugin-preserve-comment-header": "^1.0.1",
     "chai": "^4.2.0",
-    "core-js": "^3.6.4",
+    "concurrently": "^5.3.0",
     "cross-env": "^6.0.3",
     "eslint": "^7.5.0",
     "eslint-config-prettier": "^6.11.0",
@@ -60,27 +61,26 @@
     "eslint-plugin-node": "^11.1.0",
     "eslint-plugin-prettier": "^3.1.4",
     "file-loader": "^6.0.0",
-    "global-jsdom": "^6.1.0",
+    "global-jsdom": "^8.0.0",
     "husky": "^4.2.1",
     "jsdom": "^16.2.2",
     "lerna": "^3.20.2",
     "lint-staged": "^10.0.2",
     "mocha": "^8.0.1",
-    "mocha-loader": "^5.1.1",
-    "multi-entry-loader": "^1.1.2",
     "node-fetch": "^2.5.0",
     "nyc": "^15.0.0",
     "prettier": "^2.0.5",
     "resolve": "^1.15.0",
     "rimraf": "^3.0.0",
-    "typescript": "^3.9.7",
+    "typedoc": "^0.20.5",
+    "typescript": "^4.2.3",
     "web-annotation-tests": "https://github.com/w3c/web-annotation-tests",
     "webpack": "^4.41.5",
     "webpack-cli": "^3.3.10",
     "webpack-dev-server": "^3.10.1"
   },
   "engines": {
-    "node": "^10 || ^11 || ^12 || >=13.7",
+    "node": "^12.20 || ^14.15 || ^15.4 || ^16.0",
     "yarn": "^1.5.0"
   }
 }
diff --git a/packages/apache-annotator/.npmignore b/packages/apache-annotator/.npmignore
new file mode 100644
index 0000000..58fc595
--- /dev/null
+++ b/packages/apache-annotator/.npmignore
@@ -0,0 +1,5 @@
+*.d.ts.map
+tsconfig.json
+tsconfig.tsbuildinfo
+/src
+/test
diff --git a/packages/apache-annotator/package.json b/packages/apache-annotator/package.json
new file mode 100644
index 0000000..4231a26
--- /dev/null
+++ b/packages/apache-annotator/package.json
@@ -0,0 +1,28 @@
+{
+  "name": "apache-annotator",
+  "version": "0.1.0",
+  "description": "Apache Annotator provides annotation enabling code for browsers, servers, and humans.",
+  "homepage": "https://annotator.apache.org",
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/apache/incubator-annotator.git",
+    "directory": "packages/apache-annotator"
+  },
+  "license": "Apache-2.0",
+  "author": "Apache Software Foundation",
+  "type": "module",
+  "exports": {
+    "./*": "./lib/*.js"
+  },
+  "dependencies": {
+    "@apache-annotator/dom": "^0.1.0",
+    "@apache-annotator/selector": "^0.1.0",
+    "@babel/runtime-corejs3": "^7.13.10"
+  },
+  "engines": {
+    "node": "^12.20 || ^14.15 || ^15.4 || ^16.0"
+  },
+  "publishConfig": {
+    "access": "public"
+  }
+}
diff --git a/packages/dom/src/types/cartesian.d.ts b/packages/apache-annotator/src/dom.ts
similarity index 62%
copy from packages/dom/src/types/cartesian.d.ts
copy to packages/apache-annotator/src/dom.ts
index 9578e84..bed6c17 100644
--- a/packages/dom/src/types/cartesian.d.ts
+++ b/packages/apache-annotator/src/dom.ts
@@ -18,8 +18,15 @@
  * under the License.
  */
 
-declare module 'cartesian' {
-  export default function cartesian<T>(
-    list: Array<Array<T>> | { [k: string]: Array<T> },
-  ): Array<Array<T>>;
-}
+/**
+ * This module provides functions for handling annotations in the context of an
+ * HTML DOM; in other words, a web page.
+ *
+ * The module’s main functionality is *matching* (or *‘anchoring’*) a {@link https://www.w3.org/TR/2017/REC-annotation-model-20170223/#selectors
+ * | Selector} to the DOM, i.e. finding which piece of a web page it refers to;
+ * and, vice versa, *describing* a selection of the page as a Selector.
+ *
+ * @module
+ */
+
+export * from '@apache-annotator/dom';
diff --git a/packages/dom/src/types/cartesian.d.ts b/packages/apache-annotator/src/selector.ts
similarity index 62%
copy from packages/dom/src/types/cartesian.d.ts
copy to packages/apache-annotator/src/selector.ts
index 9578e84..70d464e 100644
--- a/packages/dom/src/types/cartesian.d.ts
+++ b/packages/apache-annotator/src/selector.ts
@@ -18,8 +18,16 @@
  * under the License.
  */
 
-declare module 'cartesian' {
-  export default function cartesian<T>(
-    list: Array<Array<T>> | { [k: string]: Array<T> },
-  ): Array<Array<T>>;
-}
+/**
+ * This module provides types and generic functions for handling {@link https://www.w3.org/TR/2017/REC-annotation-model-20170223/#selectors
+ * | Selector}s.
+ *
+ * Annotation tool developers should not need most of the functions contained
+ * in this module, but would instead mainly use the module made for the specific
+ * context (document type) they are dealing with. See {@link dom}, currently the
+ * only such module.
+ *
+ * @module
+ */
+
+export * from '@apache-annotator/selector';
diff --git a/packages/apache-annotator/tsconfig.json b/packages/apache-annotator/tsconfig.json
new file mode 100644
index 0000000..0ac1cf1
--- /dev/null
+++ b/packages/apache-annotator/tsconfig.json
@@ -0,0 +1,12 @@
+{
+  "extends": "../../tsconfig.base.json",
+  "include": ["src"],
+  "compilerOptions": {
+    "outDir": "lib",
+    "rootDir": "src"
+  },
+  "references": [
+    { "path": "../dom" },
+    { "path": "../selector" }
+  ]
+}
diff --git a/packages/dom/.npmignore b/packages/dom/.npmignore
index 281df39..58fc595 100644
--- a/packages/dom/.npmignore
+++ b/packages/dom/.npmignore
@@ -1,2 +1,5 @@
-src
-test
+*.d.ts.map
+tsconfig.json
+tsconfig.tsbuildinfo
+/src
+/test
diff --git a/packages/dom/@types/optimal-select/index.d.ts b/packages/dom/@types/optimal-select/index.d.ts
new file mode 100644
index 0000000..90eb3f4
--- /dev/null
+++ b/packages/dom/@types/optimal-select/index.d.ts
@@ -0,0 +1,9 @@
+// Partial declaration, just to cover the pieces we need.
+declare module 'optimal-select' {
+  export default function optimalSelect(
+    element: Element,
+    options: {
+      root: Node,
+    },
+  ): string;
+}
diff --git a/packages/dom/README.md b/packages/dom/README.md
deleted file mode 100644
index dd0f4da..0000000
--- a/packages/dom/README.md
+++ /dev/null
@@ -1,3 +0,0 @@
-This package is a part of the Apache Annotator (incubating) project.
-
-For docs and other useful info see the [website](https://annotator.apache.org/) or [GitHub repository](https://github.com/apache/incubator-annotator).
diff --git a/packages/dom/package.json b/packages/dom/package.json
index e2b198e..a6ae76d 100644
--- a/packages/dom/package.json
+++ b/packages/dom/package.json
@@ -1,32 +1,27 @@
 {
-  "name": "@annotator/dom",
+  "name": "@apache-annotator/dom",
   "version": "0.1.0",
   "description": "Utilities for annotation of the Document Object Model.",
   "homepage": "https://annotator.apache.org",
   "repository": {
     "type": "git",
-    "url": "https://github.com/apache/incubator-annotator.git"
+    "url": "https://github.com/apache/incubator-annotator.git",
+    "directory": "packages/dom"
   },
   "license": "Apache-2.0",
   "author": "Apache Software Foundation",
-  "exports": {
-    "import": "./lib/index.mjs",
-    "require": "./lib/index.js"
-  },
+  "type": "module",
+  "exports": "./lib/index.js",
   "main": "./lib/index.js",
-  "module": "./lib/index.mjs",
-  "types": "./lib/index.d.ts",
   "dependencies": {
-    "@babel/runtime-corejs3": "^7.8.7",
-    "cartesian": "^1.0.1",
-    "core-js": "^3.6.4",
-    "dom-seek": "^5.1.0"
+    "@babel/runtime-corejs3": "^7.13.10",
+    "optimal-select": "^4.0.1"
   },
   "devDependencies": {
-    "@annotator/selector": "^0.1.0"
+    "@apache-annotator/selector": "^0.1.0"
   },
   "engines": {
-    "node": "^10 || ^11 || ^12 || >=13.7"
+    "node": "^12.20 || ^14.15 || ^15.4 || ^16.0"
   },
   "publishConfig": {
     "access": "public"
diff --git a/packages/dom/src/css.ts b/packages/dom/src/css.ts
index 28ffb94..86deb91 100644
--- a/packages/dom/src/css.ts
+++ b/packages/dom/src/css.ts
@@ -18,12 +18,87 @@
  * under the License.
  */
 
-import type { CssSelector, Matcher } from '@annotator/selector';
+import optimalSelect from 'optimal-select';
+import type { CssSelector, Matcher } from '@apache-annotator/selector';
+import { ownerDocument } from './owner-document';
+import { toRange } from './to-range';
 
+/**
+ * Find the elements corresponding to the given {@link
+ * CssSelector}.
+ *
+ * The given CssSelector returns all elements within `scope` that it matches.
+ * However, the selector is evaluated relative to the Document as a whole.
+ * *(XXX is this intentional, a mistake, or compromise?)*
+ *
+ * The function is curried, taking first the selector and then the scope.
+ *
+ * As there may be multiple matches for a given selector, the matcher will
+ * return an (async) iterable that produces each match in the order they are
+ * found in the document.
+ *
+ * Note that the Web Annotation specification does not mention whether an
+ * ‘ambiguous’ CssSelector should indeed match all elements that match the
+ * selector value, or perhaps only the first. This implementation returns all
+ * matches to give users the freedom to follow either interpretation. This is
+ * also in line with more clearly defined behaviour of the TextQuoteSelector:
+ *
+ * > “If […] the user agent discovers multiple matching text sequences, then the
+ * > selection SHOULD be treated as matching all of the matches.”
+ *
+ * @param selector - The {@link CssSelector} to be anchored.
+ * @returns A {@link Matcher} function that applies `selector` to a given
+ * `scope`.
+ *
+ * @public
+ */
 export function createCssSelectorMatcher(
   selector: CssSelector,
-): Matcher<Document, Element> {
-  return async function* matchAll(scope: Document) {
-    yield* scope.querySelectorAll(selector.value);
+): Matcher<Node | Range, Element> {
+  return async function* matchAll(scope) {
+    scope = toRange(scope);
+    const document = ownerDocument(scope);
+    for (const element of document.querySelectorAll(selector.value)) {
+      const range = document.createRange();
+      range.selectNode(element);
+
+      if (
+        scope.isPointInRange(range.startContainer, range.startOffset) &&
+        scope.isPointInRange(range.endContainer, range.endOffset)
+      ) {
+        yield element;
+      }
+    }
+  };
+}
+
+/**
+ * Returns a {@link CssSelector} that unambiguously describes the given
+ * element, within the given scope.
+ *
+ * @example
+ * ```
+ * const target = document.getElementById('targetelement').firstElementChild;
+ * const selector = await describeCss(target);
+ * console.log(selector);
+ * // {
+ * //   type: 'CssSelector',
+ * //   value: '#targetelement > :nth-child(1)'
+ * // }
+ * ```
+ *
+ * @param element - The element that the selector should describe.
+ * @param scope - The node that serves as the ‘document’ for purposes of finding
+ * a unique selector. Defaults to the full Document that contains `element`.
+ * @returns The selector unambiguously describing `element` within `scope`.
+ */
+export async function describeCss(
+  element: HTMLElement,
+  scope: Node = element.ownerDocument,
+): Promise<CssSelector> {
+  const selector = optimalSelect(element, { root: scope });
+  return {
+    type: 'CssSelector',
+    value: selector,
   };
 }
diff --git a/packages/dom/src/highlight-range.ts b/packages/dom/src/highlight-range.ts
index e7b9b8e..93ff390 100644
--- a/packages/dom/src/highlight-range.ts
+++ b/packages/dom/src/highlight-range.ts
@@ -18,16 +18,29 @@
  * under the License.
  */
 
-// Wrap each text node in a given DOM Range with a <mark> or other element.
-// Breaks start and/or end node if needed.
-// Returns a function that cleans up the created highlight (not a perfect undo: split text nodes are
-// not merged again; if desired, you could run range.commonAncestorContainer.normalize() afterwards).
-//
-// Parameters:
-// - range: a DOM Range object. Note that as highlighting modifies the DOM, the range may be
-//   unusable afterwards
-// - tagName: the element used to wrap text nodes. Defaults to 'mark'.
-// - attributes: an Object defining any attributes to be set on the wrapper elements.
+import { ownerDocument } from './owner-document';
+
+/**
+ * Wrap each text node in a given DOM Range with a `<mark>` or other element.
+ *
+ * If the Range start and/or ends within a Text node, that node will be split
+ * in order to only wrap the contained part in the mark element.
+ *
+ * The highlight can be removed again by calling the function that cleans up the
+ * wrapper elements. Note that this might not perfectly restore the DOM to its
+ * previous state: text nodes that were split are not merged again. One could
+ * consider running `range.commonAncestorContainer.normalize()` afterwards to
+ * join all adjacent text nodes.
+ *
+ * @param range - A DOM Range object. Note that as highlighting modifies the
+ * DOM, the range may be unusable afterwards.
+ * @param tagName - The element used to wrap text nodes. Defaults to 'mark'.
+ * @param attributes - An object defining any attributes to be set on the
+ * wrapper elements
+ * @returns A function that removes the created highlight.
+ *
+ * @public
+ */
 export function highlightRange(
   range: Range,
   tagName = 'mark',
@@ -73,9 +86,7 @@
   }
 
   // Collect the text nodes.
-  const document =
-    range.startContainer.ownerDocument || (range.startContainer as Document);
-  const walker = document.createTreeWalker(
+  const walker = ownerDocument(range).createTreeWalker(
     range.commonAncestorContainer,
     NodeFilter.SHOW_TEXT,
     {
diff --git a/packages/dom/src/index.ts b/packages/dom/src/index.ts
index 3d7ca58..cd7d2ea 100644
--- a/packages/dom/src/index.ts
+++ b/packages/dom/src/index.ts
@@ -21,4 +21,5 @@
 export * from './css';
 export * from './range';
 export * from './text-quote';
+export * from './text-position';
 export * from './highlight-range';
diff --git a/packages/dom/src/normalize-range.ts b/packages/dom/src/normalize-range.ts
new file mode 100644
index 0000000..0b41899
--- /dev/null
+++ b/packages/dom/src/normalize-range.ts
@@ -0,0 +1,165 @@
+/**
+ * @license
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { ownerDocument } from './owner-document';
+
+/**
+ * TextRange is a Range that guarantees to always have Text nodes as its start
+ * and end nodes. To ensure the type remains correct, it also restricts usage
+ * of methods that would modify these nodes (note that a user can simply cast
+ * the TextRange back to a Range to remove these restrictions).
+ */
+export interface TextRange extends Range {
+  readonly startContainer: Text;
+  readonly endContainer: Text;
+  cloneRange(): TextRange;
+
+  // Allow only Text nodes to be passed to these methods.
+  insertNode(node: Text): void;
+  selectNodeContents(node: Text): void;
+  setEnd(node: Text, offset: number): void;
+  setStart(node: Text, offset: number): void;
+
+  // Do not allow these methods to be used at all.
+  selectNode(node: never): void;
+  setEndAfter(node: never): void;
+  setEndBefore(node: never): void;
+  setStartAfter(node: never): void;
+  setStartBefore(node: never): void;
+  surroundContents(newParent: never): void;
+}
+
+/**
+ * Normalise a {@link https://developer.mozilla.org/en-US/docs/Web/API/Range |
+ * Range} such that ranges spanning the same text become exact equals.
+ *
+ * *Note: in this context ‘text’ means any characters, including whitespace.*
+
+ * Normalises a range such that both its start and end are text nodes, and that
+ * if there are equivalent text selections it takes the narrowest option (i.e.
+ * it prefers the start not to be at the end of a text node, and vice versa).
+ *
+ * If there is no text between the start and end, they thus collapse onto one a
+ * single position; and if there are multiple equivalent positions, it takes the
+ * first one; or, if scope is passed, the first equivalent falling within scope.
+ *
+ * Note that if the given range does not contain non-empty text nodes, it may
+ * end up pointing at a text node outside of it (before it if possible, else
+ * after). If the document does not contain any text nodes, an error is thrown.
+ */
+export function normalizeRange(range: Range, scope?: Range): TextRange {
+  const document = ownerDocument(range);
+  const walker = document.createTreeWalker(document, NodeFilter.SHOW_TEXT, {
+    acceptNode(node: Text) {
+      return !scope || scope.intersectsNode(node)
+        ? NodeFilter.FILTER_ACCEPT
+        : NodeFilter.FILTER_REJECT;
+    },
+  });
+
+  let [startContainer, startOffset] = snapBoundaryPointToTextNode(
+    range.startContainer,
+    range.startOffset,
+  );
+
+  // If we point at the end of a text node, move to the start of the next one.
+  // The step is repeated to skip over empty text nodes.
+  walker.currentNode = startContainer;
+  while (startOffset === startContainer.length && walker.nextNode()) {
+    startContainer = walker.currentNode as Text;
+    startOffset = 0;
+  }
+
+  // Set the range’s start; note this might move its end too.
+  range.setStart(startContainer, startOffset);
+
+  let [endContainer, endOffset] = snapBoundaryPointToTextNode(
+    range.endContainer,
+    range.endOffset,
+  );
+
+  // If we point at the start of a text node, move to the end of the previous one.
+  // The step is repeated to skip over empty text nodes.
+  walker.currentNode = endContainer;
+  while (endOffset === 0 && walker.previousNode()) {
+    endContainer = walker.currentNode as Text;
+    endOffset = endContainer.length;
+  }
+
+  // Set the range’s end; note this might move its start too.
+  range.setEnd(endContainer, endOffset);
+
+  return range as TextRange;
+}
+
+// Given an arbitrary boundary point, this returns either:
+// - that same boundary point, if its node is a text node;
+// - otherwise the first boundary point after it whose node is a text node, if any;
+// - otherwise, the last boundary point before it whose node is a text node.
+// If the document has no text nodes, it throws an error.
+function snapBoundaryPointToTextNode(
+  node: Node,
+  offset: number,
+): [Text, number] {
+  if (isText(node)) return [node, offset];
+
+  // Find the node at or right after the boundary point.
+  let curNode: Node;
+  if (isCharacterData(node)) {
+    curNode = node;
+  } else if (offset < node.childNodes.length) {
+    curNode = node.childNodes[offset];
+  } else {
+    curNode = node;
+    while (curNode.nextSibling === null) {
+      if (curNode.parentNode === null)
+        // Boundary point is at end of document
+        throw new Error('not implemented'); // TODO
+      curNode = curNode.parentNode;
+    }
+    curNode = curNode.nextSibling;
+  }
+
+  if (isText(curNode)) return [curNode, 0];
+
+  // Walk to the next text node, or the last if there is none.
+  const document = node.ownerDocument ?? (node as Document);
+  const walker = document.createTreeWalker(document, NodeFilter.SHOW_TEXT);
+  walker.currentNode = curNode;
+  if (walker.nextNode() !== null) {
+    return [walker.currentNode as Text, 0];
+  } else if (walker.previousNode() !== null) {
+    return [walker.currentNode as Text, (walker.currentNode as Text).length];
+  } else {
+    throw new Error('Document contains no text nodes.');
+  }
+}
+
+function isText(node: Node): node is Text {
+  return node.nodeType === Node.TEXT_NODE;
+}
+
+function isCharacterData(node: Node): node is CharacterData {
+  return (
+    node.nodeType === Node.PROCESSING_INSTRUCTION_NODE ||
+    node.nodeType === Node.COMMENT_NODE ||
+    node.nodeType === Node.TEXT_NODE
+  );
+}
diff --git a/packages/dom/src/scope.ts b/packages/dom/src/owner-document.ts
similarity index 61%
rename from packages/dom/src/scope.ts
rename to packages/dom/src/owner-document.ts
index e107212..102a7de 100644
--- a/packages/dom/src/scope.ts
+++ b/packages/dom/src/owner-document.ts
@@ -18,22 +18,17 @@
  * under the License.
  */
 
-import type { DomScope } from './types';
-
-export function ownerDocument(scope: DomScope): Document {
-  const node = isRange(scope) ? scope.commonAncestorContainer : scope;
-  return node.ownerDocument || (node as Document);
+/**
+ * Get the ownerDocument for either a range or a node.
+ *
+ * @param nodeOrRange the node or range for which to get the owner document.
+ */
+export function ownerDocument(nodeOrRange: Node | Range): Document {
+  const node = isRange(nodeOrRange) ? nodeOrRange.startContainer : nodeOrRange;
+  // node.ownerDocument is null iff node is itself a Document.
+  return node.ownerDocument ?? (node as Document);
 }
 
-export function rangeFromScope(scope: DomScope): Range {
-  if (isRange(scope)) {
-    return scope;
-  }
-  const range = ownerDocument(scope).createRange();
-  range.selectNodeContents(scope);
-  return range;
-}
-
-function isRange(scope: DomScope): scope is Range {
-  return 'collapsed' in scope;
+function isRange(nodeOrRange: Node | Range): nodeOrRange is Range {
+  return 'startContainer' in nodeOrRange;
 }
diff --git a/packages/dom/src/range/cartesian.ts b/packages/dom/src/range/cartesian.ts
index 04afad5..00f88dc 100644
--- a/packages/dom/src/range/cartesian.ts
+++ b/packages/dom/src/range/cartesian.ts
@@ -18,70 +18,71 @@
  * under the License.
  */
 
-import cartesianArrays from 'cartesian';
+/**
+ * Generates the Cartesian product of the sets generated by the given iterables.
+ *
+ *   𝑆₁ × ... × 𝑆ₙ = { (𝑒₁,...,𝑒ₙ) | 𝑒ᵢ ∈ 𝑆ᵢ }
+ */
+export async function* cartesian<T>(
+  ...iterables: (Iterable<T> | AsyncIterable<T>)[]
+): AsyncGenerator<T[], void, undefined> {
+  // Create iterators for traversing each iterable and tagging every value
+  // with the index of its source iterable.
+  const iterators = iterables.map((iterable, index) => {
+    const generator = async function* () {
+      for await (const value of iterable) {
+        yield { index, value };
+      }
+      return { index };
+    };
+    return generator();
+  });
 
-export async function* product<T>(
-  ...iterables: AsyncIterable<T>[]
-): AsyncGenerator<Array<T>, void, undefined> {
-  // We listen to all iterators in parallel, while logging all the values they
-  // produce. Whenever an iterator produces a value, we produce and yield all
-  // combinations of that value with the logged values from other iterators.
-  // Every combination is thus made exactly once, and as soon as it is known.
+  try {
+    // Track the number of non-exhausted iterators.
+    let active = iterators.length;
 
-  const iterators = iterables.map((iterable) =>
-    iterable[Symbol.asyncIterator](),
-  );
-  // Initialise an empty log for each iterable.
-  const logs: T[][] = iterables.map(() => []);
+    // Track all the values of each iterator in a log.
+    const logs = iterators.map(() => []) as T[][];
 
-  type NumberedResultPromise = Promise<{
-    nextResult: IteratorResult<T>;
-    iterableNr: number;
-  }>;
+    // Track the promise of the next value of each iterator.
+    const nexts = iterators.map((it) => it.next());
 
-  function notNull(
-    p: NumberedResultPromise | null,
-  ): p is NumberedResultPromise {
-    return p !== null;
-  }
+    // Iterate the values of all the iterators in parallel and yield tuples from
+    // the partial product of each new value and the existing logs of the other
+    // iterators.
+    while (active) {
+      // Wait for the next result.
+      const result = await Promise.race(nexts);
+      const { index } = result.value;
 
-  const nextValuePromises: Array<NumberedResultPromise | null> = iterators.map(
-    (iterator, iterableNr) =>
-      iterator.next().then(
-        // Label the result with iterableNr, to know which iterable produced
-        // this value after Promise.race below.
-        (nextResult) => ({ nextResult, iterableNr }),
-      ),
-  );
+      // If the iterator has exhausted all the values, set the promise
+      // of its next value to never resolve.
+      if (result.done) {
+        active--;
+        nexts[index] = new Promise(() => undefined);
+        continue;
+      }
 
-  // Keep listening as long as any of the iterables is not yet exhausted.
-  while (nextValuePromises.some(notNull)) {
-    // Wait until any of the active iterators has produced a new value.
-    const { nextResult, iterableNr } = await Promise.race(
-      nextValuePromises.filter(notNull),
-    );
+      // Append the new value to the log.
+      const { value } = result.value;
+      logs[index].push(value);
 
-    // If this iterable was exhausted, stop listening to it and move on.
-    if (nextResult.done === true) {
-      nextValuePromises[iterableNr] = null;
-      continue;
+      // Record the promise of the next value.
+      nexts[index] = iterators[index].next();
+
+      // Create a scratch input for computing a partial product.
+      const scratch = [...logs];
+      scratch[index] = [value];
+
+      // Synchronously compute and yield tuples of the partial product.
+      yield* scratch.reduce(
+        (acc, next) => acc.flatMap((v) => next.map((w) => [...v, w])),
+        [[]] as T[][],
+      );
     }
-
-    // Produce all combinations of the received value with the logged values
-    // from the other iterables.
-    const arrays = [...logs];
-    arrays[iterableNr] = [nextResult.value];
-    const combinations: T[][] = cartesianArrays(arrays);
-
-    // Append the received value to the right log.
-    logs[iterableNr] = [...logs[iterableNr], nextResult.value];
-
-    // Start listening for the next value of this iterable.
-    nextValuePromises[iterableNr] = iterators[iterableNr]
-      .next()
-      .then((nextResult) => ({ nextResult, iterableNr }));
-
-    // Yield each of the produced combinations separately.
-    yield* combinations;
+  } finally {
+    const closeAll = iterators.map((it, index) => it.return({ index }));
+    await Promise.all(closeAll);
   }
 }
diff --git a/packages/dom/src/range/match.ts b/packages/dom/src/range/match.ts
index 87f54b4..d0b6943 100644
--- a/packages/dom/src/range/match.ts
+++ b/packages/dom/src/range/match.ts
@@ -18,32 +18,103 @@
  * under the License.
  */
 
-import type { RangeSelector, Selector, MatcherCreator, Plugin } from '@annotator/selector';
+import type {
+  Matcher,
+  Plugin,
+  RangeSelector,
+  Selector,
+  MatcherCreator,
+} from '@apache-annotator/selector';
+import { ownerDocument } from '../owner-document';
+import { toRange } from '../to-range';
+import { cartesian } from './cartesian';
 
-import { ownerDocument } from '../scope';
-import type { DomMatcher, DomScope } from '../types';
-
-import { product } from './cartesian';
-
+/**
+ * Find the range(s) corresponding to the given {@link RangeSelector}.
+ *
+ * As a RangeSelector itself nests two further selectors, one needs to pass a
+ * `createMatcher` function that will be used to process those nested selectors.
+ *
+ * The function is curried, taking first the `createMatcher` function, then the
+ * selector, and then the scope.
+ *
+ * As there may be multiple matches for the start & end selectors, the resulting
+ * matcher will return an (async) iterable, that produces a match for each
+ * possible pair of matches of the nested selectors (except those where its end
+ * would precede its start). *(Note that this behaviour is a rather free
+ * interpretation of the Web Annotation Data Model spec, which is silent about
+ * the possibility of multiple matches for RangeSelectors)*
+ *
+ * @example
+ * By using a matcher for {@link TextQuoteSelector}s, one
+ * could create a matcher for text quotes with ellipsis to select a phrase
+ * “ipsum … amet,”:
+ * ```
+ * const selector = {
+ *   type: 'RangeSelector',
+ *   startSelector: {
+ *     type: 'TextQuoteSelector',
+ *     exact: 'ipsum ',
+ *   },
+ *   endSelector: {
+ *     type: 'TextQuoteSelector',
+ *     // Because the end of a RangeSelector is *exclusive*, we will present the
+ *     // latter part of the quote as the *prefix* so it will be part of the
+ *     // match.
+ *     exact: '',
+ *     prefix: ' amet,',
+ *   }
+ * };
+ * const createRangeSelectorMatcher =
+ *   makeCreateRangeSelectorMatcher(createTextQuoteMatcher);
+ * const match = createRangeSelectorMatcher(selector)(document.body);
+ * console.log(match)
+ * // ⇒ Range { startContainer: #text, startOffset: 6, endContainer: #text,
+ * //   endOffset: 27, … }
+ * ```
+ *
+ * @example
+ * To support RangeSelectors that might themselves contain RangeSelectors,
+ * recursion can be created by supplying the resulting matcher creator function
+ * as the `createMatcher` parameter:
+ * ```
+ * const createWhicheverMatcher = (selector) => {
+ *   const innerCreateMatcher = {
+ *     TextQuoteSelector: createTextQuoteSelectorMatcher,
+ *     TextPositionSelector: createTextPositionSelectorMatcher,
+ *     RangeSelector: makeCreateRangeSelectorMatcher(createWhicheverMatcher),
+ *   }[selector.type];
+ *   return innerCreateMatcher(selector);
+ * });
+ * ```
+ *
+ * @param createMatcher - The function used to process nested selectors.
+ * @returns A function that, given a RangeSelector `selector`, creates a {@link
+ * Matcher} function that can apply it to a given `scope`.
+ *
+ * @public
+ */
 export function makeCreateRangeSelectorMatcher(
-  createMatcher: <T extends Selector>(selector: T) => DomMatcher,
-): (selector: RangeSelector) => DomMatcher {
-  return function createRangeSelectorMatcher(selector: RangeSelector) {
+  createMatcher: MatcherCreator<Node | Range, Node | Range>,
+): (selector: RangeSelector) => Matcher<Node | Range, Range> {
+  return function createRangeSelectorMatcher(selector) {
     const startMatcher = createMatcher(selector.startSelector);
     const endMatcher = createMatcher(selector.endSelector);
 
-    return async function* matchAll(scope: DomScope) {
-      const document = ownerDocument(scope);
-
+    return async function* matchAll(scope) {
       const startMatches = startMatcher(scope);
       const endMatches = endMatcher(scope);
 
-      const pairs = product(startMatches, endMatches);
+      const pairs = cartesian(startMatches, endMatches);
 
-      for await (const [start, end] of pairs) {
-        const result = document.createRange();
+      for await (let [start, end] of pairs) {
+        start = toRange(start);
+        end = toRange(end);
 
-        result.setStart(start.endContainer, start.endOffset);
+        const result = ownerDocument(scope).createRange();
+        result.setStart(start.startContainer, start.startOffset);
+        // Note that a RangeSelector’s match *excludes* the endSelector’s match,
+        // hence we take the end’s startContainer & startOffset.
         result.setEnd(end.startContainer, end.startOffset);
 
         if (!result.collapsed) yield result;
@@ -52,7 +123,7 @@
   };
 }
 
-export const supportRangeSelector: Plugin<DomScope, Range> = function supportRangeSelectorPlugin(
+export const supportRangeSelector: Plugin<Node | Range, Node | Range> = function supportRangeSelectorPlugin(
   next,
   recurse,
 ) {
diff --git a/packages/dom/src/text-node-chunker.ts b/packages/dom/src/text-node-chunker.ts
new file mode 100644
index 0000000..e02ff71
--- /dev/null
+++ b/packages/dom/src/text-node-chunker.ts
@@ -0,0 +1,170 @@
+/**
+ * @license
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import type { Chunk, Chunker, ChunkRange } from '@apache-annotator/selector';
+import { normalizeRange } from './normalize-range';
+import { ownerDocument } from './owner-document';
+import { toRange } from './to-range';
+
+export interface PartialTextNode extends Chunk<string> {
+  readonly node: Text;
+  readonly startOffset: number;
+  readonly endOffset: number;
+}
+
+export class EmptyScopeError extends TypeError {
+  constructor(message?: string) {
+    super(message || 'Scope contains no text nodes.');
+  }
+}
+
+export class OutOfScopeError extends TypeError {
+  constructor(message?: string) {
+    super(
+      message ||
+        'Cannot convert node to chunk, as it falls outside of chunker’s scope.',
+    );
+  }
+}
+
+export class TextNodeChunker implements Chunker<PartialTextNode> {
+  private scope: Range;
+  private iter: NodeIterator;
+
+  get currentChunk(): PartialTextNode {
+    const node = this.iter.referenceNode;
+
+    // This test should not actually be needed, but it keeps TypeScript happy.
+    if (!isText(node)) throw new EmptyScopeError();
+
+    return this.nodeToChunk(node);
+  }
+
+  nodeToChunk(node: Text): PartialTextNode {
+    if (!this.scope.intersectsNode(node)) throw new OutOfScopeError();
+
+    const startOffset =
+      node === this.scope.startContainer ? this.scope.startOffset : 0;
+    const endOffset =
+      node === this.scope.endContainer ? this.scope.endOffset : node.length;
+
+    return {
+      node,
+      startOffset,
+      endOffset,
+      data: node.data.substring(startOffset, endOffset),
+      equals(other) {
+        return (
+          other.node === this.node &&
+          other.startOffset === this.startOffset &&
+          other.endOffset === this.endOffset
+        );
+      },
+    };
+  }
+
+  rangeToChunkRange(range: Range): ChunkRange<PartialTextNode> {
+    range = range.cloneRange();
+
+    // Take the part of the range that falls within the scope.
+    if (range.compareBoundaryPoints(Range.START_TO_START, this.scope) === -1)
+      range.setStart(this.scope.startContainer, this.scope.startOffset);
+    if (range.compareBoundaryPoints(Range.END_TO_END, this.scope) === 1)
+      range.setEnd(this.scope.endContainer, this.scope.endOffset);
+
+    // Ensure it starts and ends at text nodes.
+    const textRange = normalizeRange(range, this.scope);
+
+    const startChunk = this.nodeToChunk(textRange.startContainer);
+    const startIndex = textRange.startOffset - startChunk.startOffset;
+    const endChunk = this.nodeToChunk(textRange.endContainer);
+    const endIndex = textRange.endOffset - endChunk.startOffset;
+
+    return { startChunk, startIndex, endChunk, endIndex };
+  }
+
+  chunkRangeToRange(chunkRange: ChunkRange<PartialTextNode>): Range {
+    const range = ownerDocument(this.scope).createRange();
+    // The `+…startOffset` parts are only relevant for the first chunk, as it
+    // might start within a text node.
+    range.setStart(
+      chunkRange.startChunk.node,
+      chunkRange.startIndex + chunkRange.startChunk.startOffset,
+    );
+    range.setEnd(
+      chunkRange.endChunk.node,
+      chunkRange.endIndex + chunkRange.endChunk.startOffset,
+    );
+    return range;
+  }
+
+  /**
+   * @param scope A Range that overlaps with at least one text node.
+   */
+  constructor(scope: Node | Range) {
+    this.scope = toRange(scope);
+    this.iter = ownerDocument(scope).createNodeIterator(
+      this.scope.commonAncestorContainer,
+      NodeFilter.SHOW_TEXT,
+      {
+        acceptNode: (node: Text) => {
+          return this.scope.intersectsNode(node)
+            ? NodeFilter.FILTER_ACCEPT
+            : NodeFilter.FILTER_REJECT;
+        },
+      },
+    );
+
+    // Move the iterator to after the start (= root) node.
+    this.iter.nextNode();
+    // If the start node is not a text node, move it to the first text node.
+    if (!isText(this.iter.referenceNode)) {
+      const nextNode = this.iter.nextNode();
+      if (nextNode === null) throw new EmptyScopeError();
+    }
+  }
+
+  nextChunk(): PartialTextNode | null {
+    // Move the iterator to after the current node, so nextNode() will cause a jump.
+    if (this.iter.pointerBeforeReferenceNode) this.iter.nextNode();
+
+    if (this.iter.nextNode()) return this.currentChunk;
+    else return null;
+  }
+
+  previousChunk(): PartialTextNode | null {
+    if (!this.iter.pointerBeforeReferenceNode) this.iter.previousNode();
+
+    if (this.iter.previousNode()) return this.currentChunk;
+    else return null;
+  }
+
+  precedesCurrentChunk(chunk: PartialTextNode): boolean {
+    if (this.currentChunk === null) return false;
+    return !!(
+      this.currentChunk.node.compareDocumentPosition(chunk.node) &
+      Node.DOCUMENT_POSITION_PRECEDING
+    );
+  }
+}
+
+function isText(node: Node): node is Text {
+  return node.nodeType === Node.TEXT_NODE;
+}
diff --git a/packages/dom/src/text-position/describe.ts b/packages/dom/src/text-position/describe.ts
new file mode 100644
index 0000000..ceaa23a
--- /dev/null
+++ b/packages/dom/src/text-position/describe.ts
@@ -0,0 +1,72 @@
+/**
+ * @license
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import type { TextPositionSelector } from '@apache-annotator/selector';
+import { describeTextPosition as abstractDescribeTextPosition } from '@apache-annotator/selector';
+import { ownerDocument } from '../owner-document';
+import { TextNodeChunker } from '../text-node-chunker';
+import { toRange } from '../to-range';
+
+/**
+ * Returns a {@link TextPositionSelector} that points at the target text within
+ * the given scope.
+ *
+ * When no scope is given, the position is described relative to the document
+ * as a whole. Note this means all the characters in all Text nodes are counted
+ * to determine the target’s position, including those in the `<head>` and
+ * whitespace, hence even a minor modification could make the selector point to
+ * a different text than its original target.
+ *
+ * @example
+ * ```
+ * const target = window.getSelection().getRangeAt(0);
+ * const selector = await describeTextPosition(target);
+ * console.log(selector);
+ * // {
+ * //   type: 'TextPositionSelector',
+ * //   start: 702,
+ * //   end: 736
+ * // }
+ * ```
+ *
+ * @param range - The {@link https://developer.mozilla.org/en-US/docs/Web/API/Range
+ * | Range} whose text content will be described.
+ * @param scope - A Node or Range that serves as the ‘document’ for purposes of
+ * finding occurrences and determining prefix and suffix. Defaults to the full
+ * Document that contains `range`.
+ * @returns The selector describing `range` within `scope`.
+ *
+ * @public
+ */
+export async function describeTextPosition(
+  range: Range,
+  scope?: Node | Range,
+): Promise<TextPositionSelector> {
+  scope = toRange(scope ?? ownerDocument(range));
+
+  const textChunks = new TextNodeChunker(scope);
+  if (textChunks.currentChunk === null)
+    throw new RangeError('Scope does not contain any Text nodes.');
+
+  return await abstractDescribeTextPosition(
+    textChunks.rangeToChunkRange(range),
+    textChunks,
+  );
+}
diff --git a/packages/dom/src/types/dom-seek.d.ts b/packages/dom/src/text-position/index.ts
similarity index 86%
rename from packages/dom/src/types/dom-seek.d.ts
rename to packages/dom/src/text-position/index.ts
index bb379b3..bb73732 100644
--- a/packages/dom/src/types/dom-seek.d.ts
+++ b/packages/dom/src/text-position/index.ts
@@ -18,9 +18,5 @@
  * under the License.
  */
 
-declare module 'dom-seek' {
-  export default function seek(
-    iter: NodeIterator,
-    where: number | Text,
-  ): number;
-}
+export * from './describe';
+export * from './match';
diff --git a/packages/dom/src/text-position/match.ts b/packages/dom/src/text-position/match.ts
new file mode 100644
index 0000000..1f83528
--- /dev/null
+++ b/packages/dom/src/text-position/match.ts
@@ -0,0 +1,68 @@
+/**
+ * @license
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import type { Matcher, TextPositionSelector } from '@apache-annotator/selector';
+import { textPositionSelectorMatcher as abstractTextPositionSelectorMatcher } from '@apache-annotator/selector';
+import { TextNodeChunker } from '../text-node-chunker';
+
+/**
+ * Find the range of text corresponding to the given {@link
+ * TextPositionSelector}.
+ *
+ * The start and end positions are measured relative to the first text character
+ * in the given scope.
+ *
+ * The function is curried, taking first the selector and then the scope.
+ *
+ * Its end result is an (async) generator producing a single {@link https://developer.mozilla.org/en-US/docs/Web/API/Range
+ * | Range} to represent the match (unlike e.g. a {@link TextQuoteSelector}, a
+ * TextPositionSelector cannot have multiple matches).
+ *
+ * @example
+ * ```
+ * const selector = { type: 'TextPositionSelector', start: 702, end: 736 };
+ * const scope = document.body;
+ * const matches = textQuoteSelectorMatcher(selector)(scope);
+ * const match = (await matches.next()).value;
+ * // ⇒ Range { startContainer: #text, startOffset: 64, endContainer: #text,
+ * //   endOffset: 98, … }
+ * ```
+ *
+ * @param selector - The {@link TextPositionSelector} to be anchored.
+ * @returns A {@link Matcher} function that applies `selector` within a given
+ * `scope`.
+ *
+ * @public
+ */
+export function createTextPositionSelectorMatcher(
+  selector: TextPositionSelector,
+): Matcher<Node | Range, Range> {
+  const abstractMatcher = abstractTextPositionSelectorMatcher(selector);
+
+  return async function* matchAll(scope) {
+    const textChunks = new TextNodeChunker(scope);
+
+    const matches = abstractMatcher(textChunks);
+
+    for await (const abstractMatch of matches) {
+      yield textChunks.chunkRangeToRange(abstractMatch);
+    }
+  };
+}
diff --git a/packages/dom/src/text-quote/describe.ts b/packages/dom/src/text-quote/describe.ts
index b048914..3beff26 100644
--- a/packages/dom/src/text-quote/describe.ts
+++ b/packages/dom/src/text-quote/describe.ts
@@ -18,182 +18,61 @@
  * under the License.
  */
 
-import seek from 'dom-seek';
-import type { TextQuoteSelector } from '@annotator/selector';
+import type {
+  TextQuoteSelector,
+  DescribeTextQuoteOptions,
+} from '@apache-annotator/selector';
+import { describeTextQuote as abstractDescribeTextQuote } from '@apache-annotator/selector';
+import { ownerDocument } from '../owner-document';
+import { TextNodeChunker } from '../text-node-chunker';
+import { toRange } from '../to-range';
 
-import type { DomScope } from '../types';
-import { ownerDocument, rangeFromScope } from '../scope';
-
+/**
+ * Returns a {@link TextQuoteSelector} that unambiguously describes the given
+ * range of text, within the given scope.
+ *
+ * The selector will contain the *exact* target quote, and in case this quote
+ * appears multiple times in the text, sufficient context around the quote will
+ * be included in the selector’s *prefix* and *suffix* attributes to
+ * disambiguate. By default, more prefix and suffix are included than strictly
+ * required; both in order to be robust against slight modifications, and in an
+ * attempt to not end halfway a word (mainly for the sake of human readability).
+ *
+ * @example
+ * ```
+ * const target = window.getSelection().getRangeAt(0);
+ * const selector = await describeTextQuote(target);
+ * console.log(selector);
+ * // {
+ * //   type: 'TextQuoteSelector',
+ * //   exact: 'ipsum',
+ * //   prefix: 'Lorem ',
+ * //   suffix: ' dolor'
+ * // }
+ * ```
+ *
+ * @param range - The {@link https://developer.mozilla.org/en-US/docs/Web/API/Range
+ * | Range} whose text content will be described
+ * @param scope - A Node or Range that serves as the ‘document’ for purposes of
+ * finding occurrences and determining prefix and suffix. Defaults to the full
+ * Document that contains `range`.
+ * @param options - Options to fine-tune the function’s behaviour.
+ * @returns The selector unambiguously describing `range` within `scope`.
+ *
+ * @public
+ */
 export async function describeTextQuote(
   range: Range,
-  scope: DomScope = ownerDocument(range).documentElement,
+  scope?: Node | Range,
+  options: DescribeTextQuoteOptions = {},
 ): Promise<TextQuoteSelector> {
-  range = range.cloneRange();
+  const scopeAsRange = toRange(scope ?? ownerDocument(range));
 
-  // Take the part of the range that falls within the scope.
-  const scopeAsRange = rangeFromScope(scope);
-  if (!scopeAsRange.isPointInRange(range.startContainer, range.startOffset))
-    range.setStart(scopeAsRange.startContainer, scopeAsRange.startOffset);
-  if (!scopeAsRange.isPointInRange(range.endContainer, range.endOffset))
-    range.setEnd(scopeAsRange.endContainer, scopeAsRange.endOffset);
+  const chunker = new TextNodeChunker(scopeAsRange);
 
-  const exact = range.toString();
-
-  const result: TextQuoteSelector = { type: 'TextQuoteSelector', exact };
-
-  const { prefix, suffix } = calculateContextForDisambiguation(
-    range,
-    result,
-    scope,
+  return await abstractDescribeTextQuote(
+    chunker.rangeToChunkRange(range),
+    () => new TextNodeChunker(scopeAsRange),
+    options,
   );
-  result.prefix = prefix;
-  result.suffix = suffix;
-
-  return result;
-}
-
-function calculateContextForDisambiguation(
-  range: Range,
-  selector: TextQuoteSelector,
-  scope: DomScope,
-): { prefix?: string; suffix?: string } {
-  const exactText = selector.exact;
-  const scopeText = rangeFromScope(scope).toString();
-  const targetStartIndex = getRangeTextPosition(range, scope);
-  const targetEndIndex = targetStartIndex + exactText.length;
-
-  // Find all matches of the text in the scope.
-  const stringMatches: number[] = [];
-  let fromIndex = 0;
-  while (fromIndex <= scopeText.length) {
-    const matchIndex = scopeText.indexOf(exactText, fromIndex);
-    if (matchIndex === -1) break;
-    stringMatches.push(matchIndex);
-    fromIndex = matchIndex + 1;
-  }
-
-  // Count for each undesired match the required prefix and suffix lengths, such that either of them
-  // would have invalidated the match.
-  const affixLengthPairs: Array<[number, number]> = [];
-  for (const matchStartIndex of stringMatches) {
-    const matchEndIndex = matchStartIndex + exactText.length;
-
-    // Skip the found match if it is the actual target.
-    if (matchStartIndex === targetStartIndex) continue;
-
-    // Count how many characters before & after them the false match and target have in common.
-    const sufficientPrefixLength = charactersNeededToBeUnique(
-      scopeText.substring(0, targetStartIndex),
-      scopeText.substring(0, matchStartIndex),
-      true,
-    );
-    const sufficientSuffixLength = charactersNeededToBeUnique(
-      scopeText.substring(targetEndIndex),
-      scopeText.substring(matchEndIndex),
-      false,
-    );
-    affixLengthPairs.push([sufficientPrefixLength, sufficientSuffixLength]);
-  }
-
-  // Find the prefix and suffix that would invalidate all mismatches, using the minimal characters
-  // for prefix and suffix combined.
-  const [prefixLength, suffixLength] = minimalSolution(affixLengthPairs);
-  const prefix = scopeText.substring(
-    targetStartIndex - prefixLength,
-    targetStartIndex,
-  );
-  const suffix = scopeText.substring(
-    targetEndIndex,
-    targetEndIndex + suffixLength,
-  );
-  return { prefix, suffix };
-}
-
-function charactersNeededToBeUnique(
-  target: string,
-  impostor: string,
-  reverse = false,
-) {
-  // Count how many characters the two strings have in common.
-  let overlap = 0;
-  const charAt = (s: string, i: number) =>
-    reverse ? s[s.length - 1 - i] : s[overlap];
-  while (
-    overlap < target.length &&
-    charAt(target, overlap) === charAt(impostor, overlap)
-  )
-    overlap++;
-  if (overlap === target.length) return Infinity;
-  // (no substring of target can make it distinguishable from its impostor)
-  else return overlap + 1;
-}
-
-function minimalSolution(
-  requirements: Array<[number, number]>,
-): [number, number] {
-  // Ensure we try solutions with an empty prefix or suffix.
-  requirements.push([0, 0]);
-
-  // Build all the pairs and order them by their sums.
-  const pairs = requirements.flatMap((l) =>
-    requirements.map<[number, number]>((r) => [l[0], r[1]]),
-  );
-  pairs.sort((a, b) => a[0] + a[1] - (b[0] + b[1]));
-
-  // Find the first pair that satisfies every requirement.
-  for (const pair of pairs) {
-    const [p0, p1] = pair;
-    if (requirements.every(([r0, r1]) => r0 <= p0 || r1 <= p1)) {
-      return pair;
-    }
-  }
-
-  // Return the largest pairing (unreachable).
-  return pairs[pairs.length - 1];
-}
-
-// Get the index of the first character of range within the text of scope.
-function getRangeTextPosition(range: Range, scope: DomScope): number {
-  const scopeAsRange = rangeFromScope(scope);
-  const iter = document.createNodeIterator(
-    scopeAsRange.commonAncestorContainer,
-    NodeFilter.SHOW_TEXT,
-    {
-      acceptNode(node: Text) {
-        // Only reveal nodes within the range
-        return scopeAsRange.intersectsNode(node)
-          ? NodeFilter.FILTER_ACCEPT
-          : NodeFilter.FILTER_REJECT;
-      },
-    },
-  );
-  const scopeOffset = isTextNode(scopeAsRange.startContainer)
-    ? scopeAsRange.startOffset
-    : 0;
-  if (isTextNode(range.startContainer))
-    return seek(iter, range.startContainer) + range.startOffset - scopeOffset;
-  else return seek(iter, firstTextNodeInRange(range)) - scopeOffset;
-}
-
-function firstTextNodeInRange(range: Range): Text {
-  // Find the first text node inside the range.
-  const iter = document.createNodeIterator(
-    range.commonAncestorContainer,
-    NodeFilter.SHOW_TEXT,
-    {
-      acceptNode(node: Text) {
-        // Only reveal nodes within the range; and skip any empty text nodes.
-        return range.intersectsNode(node) && node.length > 0
-          ? NodeFilter.FILTER_ACCEPT
-          : NodeFilter.FILTER_REJECT;
-      },
-    },
-  );
-  const node = iter.nextNode() as Text | null;
-  if (node === null) throw new Error('Range contains no text nodes');
-  return node;
-}
-
-function isTextNode(node: Node): node is Text {
-  return node.nodeType === Node.TEXT_NODE;
 }
diff --git a/packages/dom/src/text-quote/match.ts b/packages/dom/src/text-quote/match.ts
index a78d057..2b29df6 100644
--- a/packages/dom/src/text-quote/match.ts
+++ b/packages/dom/src/text-quote/match.ts
@@ -18,73 +18,66 @@
  * under the License.
  */
 
-import type { TextQuoteSelector } from '@annotator/selector';
-import seek from 'dom-seek';
+import type { Matcher, TextQuoteSelector } from '@apache-annotator/selector';
+import { textQuoteSelectorMatcher as abstractTextQuoteSelectorMatcher } from '@apache-annotator/selector';
+import { TextNodeChunker, EmptyScopeError } from '../text-node-chunker';
 
-import type { DomScope, DomMatcher } from '../types';
-import { ownerDocument, rangeFromScope } from '../scope';
-
+/**
+ * Find occurrences in a text matching the given {@link
+ * TextQuoteSelector}.
+ *
+ * This performs an exact search for the selector’s quote (including prefix and
+ * suffix) within the text contained in the given scope (a  {@link
+ * https://developer.mozilla.org/en-US/docs/Web/API/Range | Range}).
+ *
+ * Note the match is based on strict character-by-character equivalence, i.e.
+ * it is sensitive to whitespace, capitalisation, etc.
+ *
+ * The function is curried, taking first the selector and then the scope.
+ *
+ * As there may be multiple matches for a given selector (when its prefix and
+ * suffix attributes are not sufficient to disambiguate it), the matcher will
+ * return an (async) generator that produces each match in the order they are
+ * found in the text.
+ *
+ * @example
+ * ```
+ * // Find the word ‘banana’.
+ * const selector = { type: 'TextQuoteSelector', exact: 'banana' };
+ * const scope = document.body;
+ *
+ * // Read all matches.
+ * const matches = textQuoteSelectorMatcher(selector)(scope);
+ * for await (match of matches) console.log(match);
+ * // ⇒ Range { startContainer: #text, startOffset: 187, endContainer: #text,
+ * //   endOffset: 193, … }
+ * // ⇒ Range { startContainer: #text, startOffset: 631, endContainer: #text,
+ * //   endOffset: 637, … }
+ * ```
+ *
+ * @param selector - The {@link TextQuoteSelector} to be anchored.
+ * @returns A {@link Matcher} function that applies `selector` within a given
+ * `scope`.
+ *
+ * @public
+ */
 export function createTextQuoteSelectorMatcher(
   selector: TextQuoteSelector,
-): DomMatcher {
-  return async function* matchAll(scope: DomScope) {
-    const document = ownerDocument(scope);
-    const scopeAsRange = rangeFromScope(scope);
-    const scopeText = scopeAsRange.toString();
+): Matcher<Node | Range, Range> {
+  const abstractMatcher = abstractTextQuoteSelectorMatcher(selector);
 
-    const exact = selector.exact;
-    const prefix = selector.prefix || '';
-    const suffix = selector.suffix || '';
-    const searchPattern = prefix + exact + suffix;
+  return async function* matchAll(scope) {
+    let textChunks;
+    try {
+      textChunks = new TextNodeChunker(scope);
+    } catch (err) {
+      // An empty range contains no matches.
+      if (err instanceof EmptyScopeError) return;
+      else throw err;
+    }
 
-    const iter = document.createNodeIterator(
-      scopeAsRange.commonAncestorContainer,
-      NodeFilter.SHOW_TEXT,
-      {
-        acceptNode(node: Text) {
-          // Only reveal nodes within the range; and skip any empty text nodes.
-          return scopeAsRange.intersectsNode(node) && node.length > 0
-            ? NodeFilter.FILTER_ACCEPT
-            : NodeFilter.FILTER_REJECT;
-        },
-      },
-    );
-
-    // The index of the first character of iter.referenceNode inside the text.
-    let referenceNodeIndex = isTextNode(scopeAsRange.startContainer)
-      ? -scopeAsRange.startOffset
-      : 0;
-
-    let fromIndex = 0;
-    while (fromIndex <= scopeText.length) {
-      // Find the quote with its prefix and suffix in the string.
-      const patternStartIndex = scopeText.indexOf(searchPattern, fromIndex);
-      if (patternStartIndex === -1) return;
-
-      // Correct for the prefix and suffix lengths.
-      const matchStartIndex = patternStartIndex + prefix.length;
-      const matchEndIndex = matchStartIndex + exact.length;
-
-      // Create a range to represent this exact quote in the dom.
-      const match = document.createRange();
-
-      // Seek to the start of the match, make the range start there.
-      referenceNodeIndex += seek(iter, matchStartIndex - referenceNodeIndex);
-      match.setStart(iter.referenceNode, matchStartIndex - referenceNodeIndex);
-
-      // Seek to the end of the match, make the range end there.
-      referenceNodeIndex += seek(iter, matchEndIndex - referenceNodeIndex);
-      match.setEnd(iter.referenceNode, matchEndIndex - referenceNodeIndex);
-
-      // Yield the match.
-      yield match;
-
-      // Advance the search forward to detect multiple occurrences.
-      fromIndex = matchStartIndex + 1;
+    for await (const abstractMatch of abstractMatcher(textChunks)) {
+      yield textChunks.chunkRangeToRange(abstractMatch);
     }
   };
 }
-
-function isTextNode(node: Node): node is Text {
-  return node.nodeType === Node.TEXT_NODE;
-}
diff --git a/packages/dom/src/to-range.ts b/packages/dom/src/to-range.ts
new file mode 100644
index 0000000..d4cc3d5
--- /dev/null
+++ b/packages/dom/src/to-range.ts
@@ -0,0 +1,45 @@
+/**
+ * @license
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { ownerDocument } from './owner-document';
+
+/**
+ * Returns a range that exactly selects the contents of the given node.
+ *
+ * This function is idempotent: If the given argument is already a range, it
+ * simply returns that range.
+ *
+ * @param nodeOrRange The node/range to convert to a range if it is not already
+ * a range.
+ */
+export function toRange(nodeOrRange: Node | Range): Range {
+  if (isRange(nodeOrRange)) {
+    return nodeOrRange;
+  } else {
+    const node = nodeOrRange;
+    const range = ownerDocument(node).createRange();
+    range.selectNodeContents(node);
+    return range;
+  }
+}
+
+function isRange(nodeOrRange: Node | Range): nodeOrRange is Range {
+  return 'startContainer' in nodeOrRange;
+}
diff --git a/packages/dom/src/types.ts b/packages/dom/src/types.ts
deleted file mode 100644
index 9bfc80a..0000000
--- a/packages/dom/src/types.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-/**
- * @license
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import type { Matcher } from '@annotator/selector';
-
-export type DomScope = Node | Range;
-
-export type DomMatcher = Matcher<DomScope, Range>;
diff --git a/packages/dom/test/css/describe.test.ts b/packages/dom/test/css/describe.test.ts
new file mode 100644
index 0000000..711c9ce
--- /dev/null
+++ b/packages/dom/test/css/describe.test.ts
@@ -0,0 +1,58 @@
+/**
+ * @license
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { assert } from 'chai';
+import { describeCss } from '../../src/css';
+import { evaluateXPath } from '../utils';
+import { testCases } from './match-cases';
+
+const domParser = new DOMParser();
+
+describe('describeCss', () => {
+  describe('inverts test cases of css matcher', () => {
+    for (const [name, { html, scopeXPath, expected }] of Object.entries(
+      testCases,
+    )) {
+      for (let i = 0; i < expected.length; i++) {
+        const elementXPath = expected[i];
+        it(`case: '${name}' (${i + 1}/${expected.length})`, async () => {
+          const doc = domParser.parseFromString(html, 'text/html');
+          const element = evaluateXPath(doc, elementXPath) as HTMLElement;
+          const scopeElement = scopeXPath
+            ? (evaluateXPath(doc, scopeXPath) as HTMLElement)
+            : undefined;
+          const cssSelector = await describeCss(element, scopeElement);
+
+          // We do not require a specific value for the selector, just
+          // that it uniquely matches the same element again.
+          const matchingElements = (scopeElement ?? doc).querySelectorAll(
+            cssSelector.value,
+          );
+          assert.equal(
+            matchingElements.length,
+            1,
+            'Expected a selector with a single match',
+          );
+          assert.equal(matchingElements[0], element);
+        });
+      }
+    }
+  });
+});
diff --git a/packages/dom/test/css/match-cases.ts b/packages/dom/test/css/match-cases.ts
new file mode 100644
index 0000000..1cf28b9
--- /dev/null
+++ b/packages/dom/test/css/match-cases.ts
@@ -0,0 +1,56 @@
+/**
+ * @license
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import type { CssSelector } from '@apache-annotator/selector';
+
+export const testCases: {
+  [name: string]: {
+    html: string;
+    selector: CssSelector;
+    scopeXPath?: string;
+    expected: string[];
+  };
+} = {
+  simple: {
+    html: '<b>lorem <i>ipsum</i> dolor <i>amet</i> yada <i>yada</i></b>',
+    selector: {
+      type: 'CssSelector',
+      value: 'i:nth-child(2)',
+    },
+    expected: ['//b/i[2]'],
+  },
+  'multiple matches': {
+    html: '<b>lorem <i>ipsum</i> dolor <i>amet</i> yada <i>yada</i></b>',
+    selector: {
+      type: 'CssSelector',
+      value: 'i',
+    },
+    expected: ['//b/i[1]', '//b/i[2]', '//b/i[3]'],
+  },
+  'with scope': {
+    html: '<b>lorem <i>ipsum</i> dolor <u><i>amet</i> yada <i>yada</i></u></b>',
+    selector: {
+      type: 'CssSelector',
+      value: 'i',
+    },
+    scopeXPath: '//u',
+    expected: ['//u/i[1]', '//u/i[2]'],
+  },
+};
diff --git a/packages/dom/test/css/match.test.ts b/packages/dom/test/css/match.test.ts
new file mode 100644
index 0000000..ad39b42
--- /dev/null
+++ b/packages/dom/test/css/match.test.ts
@@ -0,0 +1,59 @@
+/**
+ * @license
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { assert } from 'chai';
+import type { CssSelector } from '@apache-annotator/selector';
+import { createCssSelectorMatcher } from '../../src/css';
+import { evaluateXPath } from '../utils';
+import { testCases } from './match-cases';
+
+const domParser = new DOMParser();
+
+describe('CreateCssSelectorMatcher', () => {
+  for (const [name, { html, selector, scopeXPath, expected }] of Object.entries(
+    testCases,
+  )) {
+    it(`works for case: '${name}'`, async () => {
+      const doc = domParser.parseFromString(html, 'text/html');
+
+      const scopeElement = scopeXPath ? evaluateXPath(doc, scopeXPath) : doc;
+      const scope = doc.createRange();
+      scope.selectNodeContents(scopeElement);
+
+      await testMatcher(doc, scope, selector, expected);
+    });
+  }
+});
+
+async function testMatcher(
+  doc: Document,
+  scope: Range,
+  selector: CssSelector,
+  expected: string[],
+) {
+  const matcher = createCssSelectorMatcher(selector);
+  const matches = [];
+  for await (const value of matcher(scope)) matches.push(value);
+  assert.equal(matches.length, expected.length, 'Unexpected number of matches');
+  matches.forEach((match, i) => {
+    const expectedElement = evaluateXPath(doc, expected[i]);
+    assert.equal(match, expectedElement);
+  });
+}
diff --git a/packages/dom/test/highlight-range/highlight-range.test.ts b/packages/dom/test/highlight-range/highlight-range.test.ts
index ad8a205..dae8212 100644
--- a/packages/dom/test/highlight-range/highlight-range.test.ts
+++ b/packages/dom/test/highlight-range/highlight-range.test.ts
@@ -19,11 +19,11 @@
  */
 
 import { assert } from 'chai';
-
 import { highlightRange } from '../../src/highlight-range';
-import { RangeInfo, hydrateRange, evaluateXPath } from '../utils';
+import type { RangeInfo } from '../utils';
+import { hydrateRange, evaluateXPath } from '../utils';
 
-const domParser = new window.DOMParser();
+const domParser = new DOMParser();
 
 const testCases: {
   [name: string]: {
@@ -172,7 +172,7 @@
     const inputHtml = `<b>Try highlighting this image: <img> — would that work?</b>`;
     const doc = domParser.parseFromString(inputHtml, 'text/html');
 
-    const range = document.createRange();
+    const range = doc.createRange();
     range.selectNode(evaluateXPath(doc, '//img'));
 
     const removeHighlights = highlightRange(range);
diff --git a/packages/dom/test/range/cartesian.test.ts b/packages/dom/test/range/cartesian.test.ts
index bae6027..80b8a55 100644
--- a/packages/dom/test/range/cartesian.test.ts
+++ b/packages/dom/test/range/cartesian.test.ts
@@ -19,8 +19,7 @@
  */
 
 import { assert } from 'chai';
-
-import { product } from '../../src/range/cartesian';
+import { cartesian } from '../../src/range/cartesian';
 
 async function* gen1() {
   yield 1;
@@ -38,25 +37,52 @@
 }
 
 describe('cartesian', () => {
-  describe('product', () => {
-    it('yields the cartesian product of the yielded items', async () => {
-      const cart = product(gen1(), gen2(), gen3());
+  it('yields the cartesian product of the yielded items', async () => {
+    const cart = cartesian(gen1(), gen2(), gen3());
 
-      const expected = [
-        [1, 4, 5],
-        [2, 4, 5],
-        [3, 4, 5],
-        [1, 4, 6],
-        [2, 4, 6],
-        [3, 4, 6],
-      ];
+    const expected = [
+      [1, 4, 5],
+      [2, 4, 5],
+      [3, 4, 5],
+      [1, 4, 6],
+      [2, 4, 6],
+      [3, 4, 6],
+    ];
 
-      const result: number[][] = [];
-      for await (const value of cart) {
-        result.push(value);
+    const actual: number[][] = [];
+    for await (const value of cart) {
+      actual.push(value);
+    }
+
+    assert.sameDeepMembers(actual, expected, 'yields the expected items');
+  });
+
+  it('re-raises exceptions and closes iterators', async () => {
+    let didClose = false;
+    const error = new Error();
+
+    async function* throws() {
+      yield 1;
+      throw error;
+    }
+
+    async function* works() {
+      try {
+        yield 2;
+        yield 3;
+      } finally {
+        didClose = true;
       }
+    }
 
-      assert.sameDeepMembers(result, expected, 'yields the expected items');
-    });
+    try {
+      // eslint-disable-next-line
+      const cart = cartesian(throws(), works());
+      await cart.next();
+      await cart.next();
+    } catch (e) {
+      assert.strictEqual(error, e, 're-raises an error from an iterable');
+      assert.isTrue(didClose, 'closes the iterators');
+    }
   });
 });
diff --git a/packages/dom/test/text-position/describe.test.ts b/packages/dom/test/text-position/describe.test.ts
new file mode 100644
index 0000000..34a58c8
--- /dev/null
+++ b/packages/dom/test/text-position/describe.test.ts
@@ -0,0 +1,56 @@
+/**
+ * @license
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { assert } from 'chai';
+import { describeTextPosition } from '../../src/text-position/describe';
+import { hydrateRange } from '../utils';
+import { testCases } from './match-cases';
+
+const domParser = new DOMParser();
+
+describe('createTextPositionSelectorMatcher', () => {
+  describe('inverts test cases of text position matcher', () => {
+    for (const [name, { html, selector, expected }] of Object.entries(
+      testCases,
+    )) {
+      const range = expected[0];
+      it(`case: '${name}'`, async () => {
+        const doc = domParser.parseFromString(html, 'text/html');
+        const result = await describeTextPosition(
+          hydrateRange(range, doc),
+          doc,
+        );
+        assert.deepEqual(result, selector);
+      });
+    }
+  });
+
+  it('works with a scope', () => {
+    // TODO
+  });
+
+  it('works with split text nodes', () => {
+    // TODO
+  });
+
+  it('works with code points split across text nodes', () => {
+    // TODO
+  });
+});
diff --git a/packages/dom/test/text-position/match-cases.ts b/packages/dom/test/text-position/match-cases.ts
new file mode 100644
index 0000000..1e52faa
--- /dev/null
+++ b/packages/dom/test/text-position/match-cases.ts
@@ -0,0 +1,143 @@
+/**
+ * @license
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import type { TextPositionSelector } from '@apache-annotator/selector';
+import type { RangeInfo } from '../utils';
+
+export const testCases: {
+  [name: string]: {
+    html: string;
+    selector: TextPositionSelector;
+    expected: RangeInfo[];
+  };
+} = {
+  simple: {
+    html: '<b>l😃rem ipsum dolor amet yada yada</b>',
+    selector: {
+      type: 'TextPositionSelector',
+      start: 12,
+      end: 20,
+    },
+    expected: [
+      {
+        startContainerXPath: '//b/text()',
+        startOffset: 13,
+        endContainerXPath: '//b/text()',
+        endOffset: 21,
+      },
+    ],
+  },
+  'first characters': {
+    html: '<b>l😃rem ipsum dolor amet yada yada</b>',
+    selector: {
+      type: 'TextPositionSelector',
+      start: 0,
+      end: 11,
+    },
+    expected: [
+      {
+        startContainerXPath: '//b/text()',
+        startOffset: 0,
+        endContainerXPath: '//b/text()',
+        endOffset: 12,
+      },
+    ],
+  },
+  'last characters': {
+    html: '<b>l😃rem ipsum dolor amet yada yada</b>',
+    selector: {
+      type: 'TextPositionSelector',
+      start: 23,
+      end: 32,
+    },
+    expected: [
+      {
+        startContainerXPath: '//b/text()',
+        startOffset: 24,
+        endContainerXPath: '//b/text()',
+        endOffset: 33,
+      },
+    ],
+  },
+  'across elements': {
+    html: '<b>l😃rem <i>ipsum</i> dolor <u>amet</u> yada yada</b>',
+    selector: {
+      type: 'TextPositionSelector',
+      start: 12,
+      end: 20,
+    },
+    expected: [
+      {
+        startContainerXPath: '//b/text()[2]',
+        startOffset: 1,
+        endContainerXPath: '//u/text()',
+        endOffset: 2,
+      },
+    ],
+  },
+  'exact element contents': {
+    html: '<b>l😃rem <i>ipsum dolor</i> amet yada yada</b>',
+    selector: {
+      type: 'TextPositionSelector',
+      start: 6,
+      end: 17,
+    },
+    expected: [
+      {
+        startContainerXPath: '//i/text()',
+        startOffset: 0,
+        endContainerXPath: '//b/text()[2]',
+        endOffset: 0,
+      },
+    ],
+  },
+  'text inside <head>': {
+    html: '<head><title>l😃rem ipsum dolor amet</title></head><b>yada yada</b>',
+    selector: {
+      type: 'TextPositionSelector',
+      start: 18,
+      end: 22,
+    },
+    expected: [
+      {
+        startContainerXPath: '//title/text()',
+        startOffset: 19,
+        endContainerXPath: '//b/text()[1]',
+        endOffset: 0,
+      },
+    ],
+  },
+  'empty quote': {
+    html: '<b>l😃rem</b>',
+    selector: {
+      type: 'TextPositionSelector',
+      start: 3,
+      end: 3,
+    },
+    expected: [
+      {
+        startContainerXPath: '//b/text()',
+        startOffset: 4,
+        endContainerXPath: '//b/text()',
+        endOffset: 4,
+      },
+    ],
+  },
+};
diff --git a/packages/dom/test/text-position/match.test.ts b/packages/dom/test/text-position/match.test.ts
new file mode 100644
index 0000000..f59f490
--- /dev/null
+++ b/packages/dom/test/text-position/match.test.ts
@@ -0,0 +1,215 @@
+/**
+ * @license
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { assert } from 'chai';
+import type { TextPositionSelector } from '@apache-annotator/selector';
+import { createTextPositionSelectorMatcher } from '../../src/text-position/match';
+import { evaluateXPath } from '../utils';
+import type { RangeInfo } from '../utils';
+import { testCases } from './match-cases';
+
+const domParser = new DOMParser();
+
+describe('createTextPositionSelectorMatcher', () => {
+  for (const [name, { html, selector, expected }] of Object.entries(
+    testCases,
+  )) {
+    it(`works for case: '${name}'`, async () => {
+      const doc = domParser.parseFromString(html, 'text/html');
+      await testMatcher(doc, doc, selector, expected);
+    });
+  }
+
+  it('handles adjacent text nodes', async () => {
+    const { html, selector } = testCases['simple'];
+    const doc = domParser.parseFromString(html, 'text/html');
+    const textNode = evaluateXPath(doc, '//b/text()') as Text;
+
+    textNode.splitText(16);
+    // console.log([...textNode.parentNode.childNodes].map(node => node.textContent))
+    // → [ 'l😃rem ipsum dol', 'or amet yada yada' ]
+
+    await testMatcher(doc, doc, selector, [
+      {
+        startContainerXPath: '//b/text()[1]',
+        startOffset: 13,
+        endContainerXPath: '//b/text()[2]',
+        endOffset: 5,
+      },
+    ]);
+  });
+
+  it('handles empty text nodes', async () => {
+    const { html, selector } = testCases['simple'];
+    const doc = domParser.parseFromString(html, 'text/html');
+
+    const textNode = evaluateXPath(doc, '//b/text()') as Text;
+    textNode.splitText(textNode.length);
+    textNode.splitText(21);
+    textNode.splitText(21);
+    textNode.splitText(18);
+    textNode.splitText(18);
+    textNode.splitText(13);
+    textNode.splitText(13);
+    textNode.splitText(0);
+    // console.log([...textNode.parentNode.childNodes].map(node => node.textContent))
+    // → [ '', 'l😃rem ipsum ', '', 'dolor', '', ' am', '', 'et yada yada', '' ]
+
+    await testMatcher(doc, doc, selector, [
+      {
+        startContainerXPath: '//b/text()[4]', // "dolor"
+        startOffset: 0,
+        endContainerXPath: '//b/text()[8]', // "et yada yada"
+        endOffset: 0,
+      },
+    ]);
+  });
+
+  it('works when scope spans one text node’s contents, matching its first characters', async () => {
+    const { html, selector, expected } = testCases['first characters'];
+    const doc = domParser.parseFromString(html, 'text/html');
+
+    const scope = doc.createRange();
+    scope.selectNodeContents(evaluateXPath(doc, '//b/text()'));
+
+    await testMatcher(doc, scope, selector, expected);
+  });
+
+  it('works when scope starts with an empty text node, matching its first characters', async () => {
+    const { html, selector } = testCases['first characters'];
+    const doc = domParser.parseFromString(html, 'text/html');
+
+    const textNode = evaluateXPath(doc, '//b/text()') as Text;
+    textNode.splitText(0);
+
+    const scope = doc.createRange();
+    scope.selectNodeContents(evaluateXPath(doc, '//b'));
+
+    await testMatcher(doc, scope, selector, [
+      {
+        startContainerXPath: '//b/text()[2]',
+        startOffset: 0,
+        endContainerXPath: '//b/text()[2]',
+        endOffset: 12,
+      },
+    ]);
+  });
+
+  it('works when scope has both ends within one text node', async () => {
+    const { html, expected } = testCases['simple'];
+
+    const doc = domParser.parseFromString(html, 'text/html');
+
+    // Use the substring ‘ipsum dolor amet’ as scope.
+    const scope = doc.createRange();
+    scope.setStart(evaluateXPath(doc, '//b/text()'), 7);
+    scope.setEnd(evaluateXPath(doc, '//b/text()'), 23);
+
+    const selector: TextPositionSelector = {
+      type: 'TextPositionSelector',
+      start: 6,
+      end: 14,
+    };
+
+    await testMatcher(doc, scope, selector, expected);
+  });
+
+  it('works when scope has both ends inside text nodes', async () => {
+    const { html, expected } = testCases['across elements'];
+    const doc = domParser.parseFromString(html, 'text/html');
+
+    // Use the substring ‘sum dolor am’ as scope.
+    const scope = doc.createRange();
+    scope.setStart(evaluateXPath(doc, '//i/text()'), 2);
+    scope.setEnd(evaluateXPath(doc, '//u/text()'), 2);
+
+    const selector: TextPositionSelector = {
+      type: 'TextPositionSelector',
+      start: 4,
+      end: 12,
+    };
+
+    await testMatcher(doc, scope, selector, expected);
+  });
+
+  it('works when scope has both ends inside an element', async () => {
+    const { html, expected } = testCases['across elements'];
+    const doc = domParser.parseFromString(html, 'text/html');
+
+    const scope = doc.createRange();
+    scope.setStart(evaluateXPath(doc, '//b'), 1); // before the <i>
+    scope.setEnd(evaluateXPath(doc, '//b'), 4); // before the " yada yada"
+    const selector: TextPositionSelector = {
+      type: 'TextPositionSelector',
+      start: 6,
+      end: 14,
+    };
+    await testMatcher(doc, scope, selector, expected);
+  });
+});
+
+async function testMatcher(
+  doc: Document,
+  scope: Node | Range,
+  selector: TextPositionSelector,
+  expected: RangeInfo[],
+) {
+  const matcher = createTextPositionSelectorMatcher(selector);
+  const matches = [];
+  for await (const value of matcher(scope)) matches.push(value);
+  assert.equal(matches.length, expected.length);
+  matches.forEach((match, i) => {
+    const expectedRange = expected[i];
+    const expectedStartContainer = evaluateXPath(
+      doc,
+      expectedRange.startContainerXPath,
+    );
+    const expectedEndContainer = evaluateXPath(
+      doc,
+      expectedRange.endContainerXPath,
+    );
+    assert(
+      match.startContainer === expectedStartContainer,
+      `unexpected start container: ${prettyNodeName(match.startContainer)}; ` +
+        `expected ${prettyNodeName(expectedStartContainer)}`,
+    );
+    assert.equal(match.startOffset, expectedRange.startOffset);
+    assert(
+      match.endContainer ===
+        evaluateXPath(doc, expectedRange.endContainerXPath),
+      `unexpected end container: ${prettyNodeName(match.endContainer)}; ` +
+        `expected ${prettyNodeName(expectedEndContainer)}`,
+    );
+    assert.equal(match.endOffset, expectedRange.endOffset);
+  });
+}
+
+function prettyNodeName(node: Node) {
+  switch (node.nodeType) {
+    case Node.TEXT_NODE: {
+      const text = (node as Text).nodeValue || '';
+      return `#text "${text.length > 50 ? text.substring(0, 50) + '…' : text}"`;
+    }
+    case Node.ELEMENT_NODE:
+      return `<${(node as Element).tagName.toLowerCase()}>`;
+    default:
+      return node.nodeName.toLowerCase();
+  }
+}
diff --git a/packages/dom/test/text-quote/describe-cases.ts b/packages/dom/test/text-quote/describe-cases.ts
index 7d43a5f..2ca29e6 100644
--- a/packages/dom/test/text-quote/describe-cases.ts
+++ b/packages/dom/test/text-quote/describe-cases.ts
@@ -18,18 +18,55 @@
  * under the License.
  */
 
-import type { TextQuoteSelector } from '@annotator/selector';
+import type {
+  TextQuoteSelector,
+  DescribeTextQuoteOptions,
+} from '@apache-annotator/selector';
+import type { RangeInfo } from '../utils';
 
-import { RangeInfo } from '../utils';
-
-export const testCases: {
+export interface DescribeTextQuoteTestCases {
   [name: string]: {
     html: string;
     range: RangeInfo;
+    options: DescribeTextQuoteOptions;
     expected: TextQuoteSelector;
   };
-} = {
-  simple: {
+}
+
+export const testCasesWithoutOptions: DescribeTextQuoteTestCases = {
+  'no context': {
+    html: '<b>lorem ipsum dolor amet yada yada</b>',
+    range: {
+      startContainerXPath: '//b/text()',
+      startOffset: 12,
+      endContainerXPath: '//b/text()',
+      endOffset: 22,
+    },
+    options: {},
+    expected: {
+      type: 'TextQuoteSelector',
+      exact: 'dolor amet',
+      prefix: '',
+      suffix: '',
+    },
+  },
+  'use prefix to complete word': {
+    html: '<b>lorem ipsum dolor amet yada yada</b>',
+    range: {
+      startContainerXPath: '//b/text()',
+      startOffset: 14,
+      endContainerXPath: '//b/text()',
+      endOffset: 22,
+    },
+    options: {},
+    expected: {
+      type: 'TextQuoteSelector',
+      exact: 'lor amet',
+      prefix: 'do',
+      suffix: '',
+    },
+  },
+  'use suffix to complete word': {
     html: '<b>lorem ipsum dolor amet yada yada</b>',
     range: {
       startContainerXPath: '//b/text()',
@@ -37,6 +74,124 @@
       endContainerXPath: '//b/text()',
       endOffset: 20,
     },
+    options: {},
+    expected: {
+      type: 'TextQuoteSelector',
+      exact: 'dolor am',
+      prefix: '',
+      suffix: 'et',
+    },
+  },
+  'add context to disambiguate': {
+    html: '<b>To annotate or not to annotate</b>',
+    range: {
+      startContainerXPath: '//b/text()',
+      startOffset: 15,
+      endContainerXPath: '//b/text()',
+      endOffset: 18,
+    },
+    options: {},
+    expected: {
+      type: 'TextQuoteSelector',
+      exact: 'not',
+      prefix: 'or ',
+      suffix: ' to',
+    },
+  },
+  'only prefix for end of text': {
+    html: '<b>To annotate or not to annotate</b>',
+    range: {
+      startContainerXPath: '//b/text()',
+      startOffset: 22,
+      endContainerXPath: '//b/text()',
+      endOffset: 30,
+    },
+    options: {},
+    expected: {
+      type: 'TextQuoteSelector',
+      exact: 'annotate',
+      prefix: 'to ',
+      suffix: '',
+    },
+  },
+  'only suffix for start of text': {
+    html: '<b>annotate or not to annotate, yada yada</b>',
+    range: {
+      startContainerXPath: '//b/text()',
+      startOffset: 0,
+      endContainerXPath: '//b/text()',
+      endOffset: 8,
+    },
+    options: {},
+    expected: {
+      type: 'TextQuoteSelector',
+      exact: 'annotate',
+      prefix: '',
+      suffix: ' or',
+    },
+  },
+  'multiple, overlapping false matches': {
+    html: '<b>a a a a a a a a a a</b>',
+    range: {
+      startContainerXPath: '//b/text()',
+      startOffset: 8,
+      endContainerXPath: '//b/text()',
+      endOffset: 13,
+    },
+    options: {},
+    expected: {
+      type: 'TextQuoteSelector',
+      exact: 'a a a',
+      prefix: 'a a a a ',
+      suffix: ' a a a',
+    },
+  },
+  'empty quote': {
+    html: '<b>To annotate or not to annotate</b>',
+    range: {
+      startContainerXPath: '//b/text()',
+      startOffset: 11,
+      endContainerXPath: '//b/text()',
+      endOffset: 11,
+    },
+    options: {},
+    expected: {
+      type: 'TextQuoteSelector',
+      exact: '',
+      prefix: 'To annotate',
+      suffix: ' or',
+    },
+  },
+  'across elements': {
+    html: '<b>To annotate or <i>not</i> to <u>anno</u>tat</b>e',
+    range: {
+      startContainerXPath: '//u/text()',
+      startOffset: 0,
+      endContainerXPath: '//b/text()[3]',
+      endOffset: 2,
+    },
+    options: {},
+    expected: {
+      type: 'TextQuoteSelector',
+      exact: 'annota',
+      prefix: 'to ',
+      suffix: 'te',
+    },
+  },
+};
+
+export const testCasesWithMinimalContext: DescribeTextQuoteTestCases = {
+  'no context': {
+    html: '<b>lorem ipsum dolor amet yada yada</b>',
+    range: {
+      startContainerXPath: '//b/text()',
+      startOffset: 12,
+      endContainerXPath: '//b/text()',
+      endOffset: 20,
+    },
+    options: {
+      minimalContext: true,
+    },
     expected: {
       type: 'TextQuoteSelector',
       exact: 'dolor am',
@@ -52,6 +207,9 @@
       endContainerXPath: '//b/text()',
       endOffset: 26,
     },
+    options: {
+      minimalContext: true,
+    },
     expected: {
       type: 'TextQuoteSelector',
       exact: 'anno',
@@ -67,6 +225,9 @@
       endContainerXPath: '//b/text()',
       endOffset: 11,
     },
+    options: {
+      minimalContext: true,
+    },
     expected: {
       type: 'TextQuoteSelector',
       exact: 'tate',
@@ -82,6 +243,9 @@
       endContainerXPath: '//b/text()',
       endOffset: 2,
     },
+    options: {
+      minimalContext: true,
+    },
     expected: {
       type: 'TextQuoteSelector',
       exact: 'to',
@@ -97,6 +261,9 @@
       endContainerXPath: '//b/text()',
       endOffset: 30,
     },
+    options: {
+      minimalContext: true,
+    },
     expected: {
       type: 'TextQuoteSelector',
       exact: 'tate',
@@ -104,6 +271,24 @@
       suffix: '',
     },
   },
+  'multiple, overlapping false matches': {
+    html: '<b>aaaaaaaaaa</b>',
+    range: {
+      startContainerXPath: '//b/text()',
+      startOffset: 4,
+      endContainerXPath: '//b/text()',
+      endOffset: 7,
+    },
+    options: {
+      minimalContext: true,
+    },
+    expected: {
+      type: 'TextQuoteSelector',
+      exact: 'aaa',
+      prefix: 'aaaa',
+      suffix: 'aaa',
+    },
+  },
   'empty quote': {
     html: '<b>To annotate or not to annotate</b>',
     range: {
@@ -112,6 +297,9 @@
       endContainerXPath: '//b/text()',
       endOffset: 11,
     },
+    options: {
+      minimalContext: true,
+    },
     expected: {
       type: 'TextQuoteSelector',
       exact: '',
@@ -119,19 +307,117 @@
       suffix: ' ',
     },
   },
-  'across elements': {
-    html: '<b>To annotate or <i>not</i> to <u>anno</u>tate</b>',
+};
+
+export const testCasesWithMinimumQuoteLength: DescribeTextQuoteTestCases = {
+  'balance prefix and suffix': {
+    html: '<b>lorem ipsum dolor amet yada yada</b>',
     range: {
-      startContainerXPath: '//u/text()',
-      startOffset: 0,
-      endContainerXPath: '//b/text()[3]',
-      endOffset: 2,
+      startContainerXPath: '//b/text()',
+      startOffset: 12,
+      endContainerXPath: '//b/text()',
+      endOffset: 17,
+    },
+    options: {
+      minimumQuoteLength: 10,
     },
     expected: {
       type: 'TextQuoteSelector',
-      exact: 'annota',
-      prefix: 'to ',
-      suffix: '',
+      exact: 'dolor',
+      prefix: 'ipsum ',
+      suffix: ' amet',
+    },
+  },
+  'use prefix for end of text': {
+    html: '<b>lorem ipsum dolor amet yada yada</b>',
+    range: {
+      startContainerXPath: '//b/text()',
+      startOffset: 28,
+      endContainerXPath: '//b/text()',
+      endOffset: 30,
+    },
+    options: {
+      minimumQuoteLength: 10,
+    },
+    expected: {
+      type: 'TextQuoteSelector',
+      exact: 'ya',
+      prefix: 'amet yada ',
+      suffix: 'da',
+    },
+  },
+  'use suffix for start of text': {
+    html: '<b>lorem ipsum dolor amet yada yada</b>',
+    range: {
+      startContainerXPath: '//b/text()',
+      startOffset: 2,
+      endContainerXPath: '//b/text()',
+      endOffset: 3,
+    },
+    options: {
+      minimumQuoteLength: 10,
+    },
+    expected: {
+      type: 'TextQuoteSelector',
+      exact: 'r',
+      prefix: 'lo',
+      suffix: 'em ipsum',
+    },
+  },
+};
+
+export const testCasesWithMaxWordLength: DescribeTextQuoteTestCases = {
+  'too long prefix': {
+    html: '<b>Surely counterantidisintermediationism is too long to quote.</b>',
+    range: {
+      startContainerXPath: '//b/text()',
+      startOffset: 28,
+      endContainerXPath: '//b/text()',
+      endOffset: 31,
+    },
+    options: {
+      maxWordLength: 10,
+    },
+    expected: {
+      type: 'TextQuoteSelector',
+      exact: 'dia',
+      prefix: 'disinterme',
+      suffix: 'tionism',
+    },
+  },
+  'too long suffix': {
+    html: '<b>Surely counterantidisintermediationism is too long to quote.</b>',
+    range: {
+      startContainerXPath: '//b/text()',
+      startOffset: 14,
+      endContainerXPath: '//b/text()',
+      endOffset: 18,
+    },
+    options: {
+      maxWordLength: 10,
+    },
+    expected: {
+      type: 'TextQuoteSelector',
+      exact: 'anti',
+      prefix: 'counter',
+      suffix: 'disinterme',
+    },
+  },
+  'default should be 50': {
+    html:
+      '<b>The chromosome is ACATATTACGTTAGATATGACACCCATATAGTTATTTATAAGATGGGACAGATATTAGTTTAAAAA</b>',
+    range: {
+      startContainerXPath: '//b/text()',
+      startOffset: 18,
+      endContainerXPath: '//b/text()',
+      endOffset: 27,
+    },
+    options: {},
+    expected: {
+      type: 'TextQuoteSelector',
+      exact: 'ACATATTAC',
+      prefix: '',
+      suffix: 'GTTAGATATGACACCCATATAGTTATTTATAAGATGGGACAGATATTAGT',
     },
   },
 };
diff --git a/packages/dom/test/text-quote/describe.test.ts b/packages/dom/test/text-quote/describe.test.ts
index 9219147..ac48ed2 100644
--- a/packages/dom/test/text-quote/describe.test.ts
+++ b/packages/dom/test/text-quote/describe.test.ts
@@ -19,31 +19,65 @@
  */
 
 import { assert } from 'chai';
-
 import { describeTextQuote } from '../../src/text-quote/describe';
 import { hydrateRange, evaluateXPath } from '../utils';
-
-import { testCases } from './describe-cases';
+import type { DescribeTextQuoteTestCases } from './describe-cases';
+import {
+  testCasesWithMinimumQuoteLength,
+  testCasesWithMaxWordLength,
+  testCasesWithMinimalContext,
+  testCasesWithoutOptions,
+} from './describe-cases';
 import { testCases as testMatchCases } from './match-cases';
 
-const domParser = new window.DOMParser();
+const domParser = new DOMParser();
 
-describe('describeTextQuote', () => {
-  for (const [name, { html, range, expected }] of Object.entries(testCases)) {
+function runTestCases(testCases: DescribeTextQuoteTestCases) {
+  for (const [name, { html, range, expected, options }] of Object.entries(
+    testCases,
+  )) {
     it(`works for case: ${name}`, async () => {
       const doc = domParser.parseFromString(html, 'text/html');
-      const result = await describeTextQuote(hydrateRange(range, doc), doc);
+      const result = await describeTextQuote(
+        hydrateRange(range, doc),
+        doc,
+        options,
+      );
       assert.deepEqual(result, expected);
     });
   }
+}
+
+describe('describeTextQuote', () => {
+  describe('without options', () => {
+    runTestCases(testCasesWithoutOptions);
+  });
+
+  describe('with minimal context', () => {
+    runTestCases(testCasesWithMinimalContext);
+  });
+
+  describe('with minimum quote length', () => {
+    runTestCases(testCasesWithMinimumQuoteLength);
+  });
+
+  describe('with max word length', () => {
+    runTestCases(testCasesWithMaxWordLength);
+  });
 
   it('works with custom scope', async () => {
-    const { html, range } = testCases['minimal prefix'];
+    const { html, range, options } = testCasesWithMinimalContext[
+      'minimal prefix'
+    ];
     const doc = domParser.parseFromString(html, 'text/html');
     const scope = doc.createRange();
     scope.setStart(evaluateXPath(doc, '//b/text()'), 15);
     scope.setEnd(evaluateXPath(doc, '//b/text()'), 30); // "not to annotate"
-    const result = await describeTextQuote(hydrateRange(range, doc), scope);
+    const result = await describeTextQuote(
+      hydrateRange(range, doc),
+      scope,
+      options,
+    );
     assert.deepEqual(result, {
       type: 'TextQuoteSelector',
       exact: 'anno',
@@ -53,12 +87,16 @@
   });
 
   it('strips part of the range outside the scope', async () => {
-    const { html, range } = testCases['simple'];
+    const { html, range, options } = testCasesWithMinimalContext['no context'];
     const doc = domParser.parseFromString(html, 'text/html');
     const scope = doc.createRange();
     scope.setStart(evaluateXPath(doc, '//b/text()'), 6);
     scope.setEnd(evaluateXPath(doc, '//b/text()'), 17); // "ipsum dolor"
-    const result = await describeTextQuote(hydrateRange(range, doc), scope);
+    const result = await describeTextQuote(
+      hydrateRange(range, doc),
+      scope,
+      options,
+    );
     assert.deepEqual(result, {
       type: 'TextQuoteSelector',
       exact: 'dolor',
@@ -68,15 +106,32 @@
   });
 
   it('works if the range equals the scope', async () => {
-    const { html, range, expected } = testCases['simple'];
+    const { html, range, expected, options } = testCasesWithMinimalContext[
+      'no context'
+    ];
     const doc = domParser.parseFromString(html, 'text/html');
     const result = await describeTextQuote(
       hydrateRange(range, doc),
       hydrateRange(range, doc),
+      options,
     );
     assert.deepEqual(result, expected);
   });
 
+  it('works if range does not contain Text nodes', async () => {
+    const html = `<b>Try quoting this image: <img/> — would that work?</b>`
+    const doc = domParser.parseFromString(html, 'text/html');
+    const range = document.createRange();
+    range.selectNode(evaluateXPath(doc, '//img'));
+    const result = await describeTextQuote(range, doc);
+    assert.deepEqual(result, {
+      type: 'TextQuoteSelector',
+      exact: '',
+      prefix: 'image: ',
+      suffix: ' —',
+    });
+  });
+
   describe('inverts test cases of text quote matcher', () => {
     const applicableTestCases = Object.entries(testMatchCases).filter(
       ([_, { expected }]) => expected.length > 0,
diff --git a/packages/dom/test/text-quote/match-cases.ts b/packages/dom/test/text-quote/match-cases.ts
index 099802c..8d344d3 100644
--- a/packages/dom/test/text-quote/match-cases.ts
+++ b/packages/dom/test/text-quote/match-cases.ts
@@ -18,9 +18,8 @@
  * under the License.
  */
 
-import type { TextQuoteSelector } from '@annotator/selector';
-
-import { RangeInfo } from '../utils';
+import type { TextQuoteSelector } from '@apache-annotator/selector';
+import type { RangeInfo } from '../utils';
 
 export const testCases: {
   [name: string]: {
@@ -99,8 +98,8 @@
       {
         startContainerXPath: '//i/text()',
         startOffset: 0,
-        endContainerXPath: '//b/text()[2]',
-        endOffset: 0,
+        endContainerXPath: '//i/text()',
+        endOffset: 11,
       },
     ],
   },
@@ -115,8 +114,8 @@
       {
         startContainerXPath: '//title/text()',
         startOffset: 4,
-        endContainerXPath: '//b/text()[1]',
-        endOffset: 0,
+        endContainerXPath: '//title/text()',
+        endOffset: 9,
       },
     ],
   },
@@ -302,6 +301,51 @@
         endOffset: i,
       })),
   },
+  'empty quote, multiple elements': {
+    html: '<b>l<i>or</i>em</b>',
+    selector: {
+      type: 'TextQuoteSelector',
+      exact: '',
+    },
+    expected: [
+      {
+        startContainerXPath: '//b/text()[1]',
+        startOffset: 0,
+        endContainerXPath: '//b/text()[1]',
+        endOffset: 0,
+      },
+      {
+        startContainerXPath: '//b/text()[1]',
+        startOffset: 1,
+        endContainerXPath: '//b/text()[1]',
+        endOffset: 1,
+      },
+      {
+        startContainerXPath: '//i/text()',
+        startOffset: 1,
+        endContainerXPath: '//i/text()',
+        endOffset: 1,
+      },
+      {
+        startContainerXPath: '//i/text()',
+        startOffset: 2,
+        endContainerXPath: '//i/text()',
+        endOffset: 2,
+      },
+      {
+        startContainerXPath: '//b/text()[2]',
+        startOffset: 1,
+        endContainerXPath: '//b/text()[2]',
+        endOffset: 1,
+      },
+      {
+        startContainerXPath: '//b/text()[2]',
+        startOffset: 2,
+        endContainerXPath: '//b/text()[2]',
+        endOffset: 2,
+      },
+    ],
+  },
   'empty quote, with prefix': {
     html: '<b>lorem ipsum dolor amet yada yada</b>',
     selector: {
diff --git a/packages/dom/test/text-quote/match.test.ts b/packages/dom/test/text-quote/match.test.ts
index 78b1239..f023215 100644
--- a/packages/dom/test/text-quote/match.test.ts
+++ b/packages/dom/test/text-quote/match.test.ts
@@ -19,15 +19,13 @@
  */
 
 import { assert } from 'chai';
-import type { TextQuoteSelector } from '@annotator/selector';
-
+import type { TextQuoteSelector } from '@apache-annotator/selector';
 import { createTextQuoteSelectorMatcher } from '../../src/text-quote/match';
-import type { DomScope } from '../../src/types';
-import { evaluateXPath, RangeInfo } from '../utils';
-
+import { evaluateXPath } from '../utils';
+import type { RangeInfo } from '../utils';
 import { testCases } from './match-cases';
 
-const domParser = new window.DOMParser();
+const domParser = new DOMParser();
 
 describe('createTextQuoteSelectorMatcher', () => {
   for (const [name, { html, selector, expected }] of Object.entries(
@@ -53,8 +51,8 @@
       {
         startContainerXPath: '//b/text()[13]',
         startOffset: 0,
-        endContainerXPath: '//b/text()[21]',
-        endOffset: 0,
+        endContainerXPath: '//b/text()[20]',
+        endOffset: 1,
       },
     ]);
   });
@@ -62,7 +60,6 @@
   it('handles empty text nodes', async () => {
     const { html, selector } = testCases['simple'];
     const doc = domParser.parseFromString(html, 'text/html');
-
     const textNode = evaluateXPath(doc, '//b/text()') as Text;
     textNode.splitText(textNode.length);
     textNode.splitText(20);
@@ -79,34 +76,33 @@
       {
         startContainerXPath: '//b/text()[4]', // "dolor"
         startOffset: 0,
-        endContainerXPath: '//b/text()[8]', // "et yada yada"
-        endOffset: 0,
+        endContainerXPath: '//b/text()[6]', // " am"
+        endOffset: 3,
       },
     ]);
   });
 
-  it('works with parent of text as scope', async () => {
-    const { html, selector, expected } = testCases['simple'];
-    const doc = domParser.parseFromString(html, 'text/html');
-
-    await testMatcher(doc, evaluateXPath(doc, '//b'), selector, expected);
-  });
-
-  it('works with parent of text as scope, when matching its first characters', async () => {
+  it('works when scope spans one text node’s contents, matching its first characters', async () => {
     const { html, selector, expected } = testCases['first characters'];
     const doc = domParser.parseFromString(html, 'text/html');
 
-    await testMatcher(doc, evaluateXPath(doc, '//b'), selector, expected);
+    const scope = doc.createRange();
+    scope.selectNodeContents(evaluateXPath(doc, '//b/text()'));
+
+    await testMatcher(doc, scope, selector, expected);
   });
 
-  it('works with parent of text as scope, when matching its first characters, with an empty text node', async () => {
+  it('works when scope starts with an empty text node, matching its first characters', async () => {
     const { html, selector } = testCases['first characters'];
     const doc = domParser.parseFromString(html, 'text/html');
 
     const textNode = evaluateXPath(doc, '//b/text()') as Text;
     textNode.splitText(0);
 
-    await testMatcher(doc, evaluateXPath(doc, '//b'), selector, [
+    const scope = doc.createRange();
+    scope.selectNodeContents(evaluateXPath(doc, '//b'));
+
+    await testMatcher(doc, scope, selector, [
       {
         startContainerXPath: '//b/text()[2]',
         startOffset: 0,
@@ -116,7 +112,7 @@
     ]);
   });
 
-  it('works when scope is a Range within one text node', async () => {
+  it('works when scope has both ends within one text node', async () => {
     const { html, selector, expected } = testCases['simple'];
     const doc = domParser.parseFromString(html, 'text/html');
 
@@ -127,7 +123,7 @@
     await testMatcher(doc, scope, selector, expected);
   });
 
-  it('works when scope is a Range with both ends inside text nodes', async () => {
+  it('works when scope has both ends inside text nodes', async () => {
     const { html, selector, expected } = testCases['across elements'];
     const doc = domParser.parseFromString(html, 'text/html');
 
@@ -138,7 +134,7 @@
     await testMatcher(doc, scope, selector, expected);
   });
 
-  it('works when scope is a Range with both ends inside elements', async () => {
+  it('works when scope has both ends inside an element', async () => {
     const { html, selector, expected } = testCases['across elements'];
     const doc = domParser.parseFromString(html, 'text/html');
 
@@ -179,14 +175,14 @@
 
 async function testMatcher(
   doc: Document,
-  scope: DomScope,
+  scope: Node | Range,
   selector: TextQuoteSelector,
   expected: RangeInfo[],
 ) {
   const matcher = createTextQuoteSelectorMatcher(selector);
   const matches = [];
   for await (const value of matcher(scope)) matches.push(value);
-  assert.equal(matches.length, expected.length);
+  assert.equal(matches.length, expected.length, 'Wrong number of matches.');
   matches.forEach((match, i) => {
     const expectedRange = expected[i];
     const expectedStartContainer = evaluateXPath(
diff --git a/packages/dom/tsconfig.json b/packages/dom/tsconfig.json
index 02e0fcf..e26d55e 100644
--- a/packages/dom/tsconfig.json
+++ b/packages/dom/tsconfig.json
@@ -1,6 +1,6 @@
 {
   "extends": "../../tsconfig.base.json",
-  "include": ["src"],
+  "include": ["src", "./@types"],
   "compilerOptions": {
     "outDir": "lib",
     "rootDir": "src"
diff --git a/packages/selector/.npmignore b/packages/selector/.npmignore
index 281df39..58fc595 100644
--- a/packages/selector/.npmignore
+++ b/packages/selector/.npmignore
@@ -1,2 +1,5 @@
-src
-test
+*.d.ts.map
+tsconfig.json
+tsconfig.tsbuildinfo
+/src
+/test
diff --git a/packages/selector/README.md b/packages/selector/README.md
deleted file mode 100644
index dd0f4da..0000000
--- a/packages/selector/README.md
+++ /dev/null
@@ -1,3 +0,0 @@
-This package is a part of the Apache Annotator (incubating) project.
-
-For docs and other useful info see the [website](https://annotator.apache.org/) or [GitHub repository](https://github.com/apache/incubator-annotator).
diff --git a/packages/selector/package.json b/packages/selector/package.json
index b3b4c47..e940911 100644
--- a/packages/selector/package.json
+++ b/packages/selector/package.json
@@ -1,27 +1,23 @@
 {
-  "name": "@annotator/selector",
+  "name": "@apache-annotator/selector",
   "version": "0.1.0",
   "description": "Web Annotation selector for engine.",
   "homepage": "https://annotator.apache.org",
   "repository": {
     "type": "git",
-    "url": "https://github.com/apache/incubator-annotator.git"
+    "url": "https://github.com/apache/incubator-annotator.git",
+    "directory": "packages/selector"
   },
   "license": "Apache-2.0",
   "author": "Apache Software Foundation",
-  "exports": {
-    "import": "./lib/index.mjs",
-    "require": "./lib/index.js"
-  },
+  "type": "module",
+  "exports": "./lib/index.js",
   "main": "./lib/index.js",
-  "module": "./lib/index.mjs",
-  "types": "./lib/index.d.ts",
   "dependencies": {
-    "@babel/runtime-corejs3": "^7.8.7",
-    "core-js": "^3.6.4"
+    "@babel/runtime-corejs3": "^7.13.10"
   },
   "engines": {
-    "node": "^10 || ^11 || ^12 || >=13.7"
+    "node": "^12.20 || ^14.15 || ^15.4 || ^16.0"
   },
   "publishConfig": {
     "access": "public"
diff --git a/packages/selector/src/index.ts b/packages/selector/src/index.ts
index 2214711..7c294ae 100644
--- a/packages/selector/src/index.ts
+++ b/packages/selector/src/index.ts
@@ -20,6 +20,7 @@
 
 import type { Matcher, Selector, SelectorType, MatcherCreator, Plugin } from './types';
 
+export * from './text';
 export * from './types';
 
 interface TypeToMatcherCreatorMap<TScope, TMatch> {
@@ -68,7 +69,14 @@
   }
 }
 
-// A plugin to support the Selector’s refinedBy field .
+/**
+ * A plugin to support the Selector’s refinedBy field.
+ *
+ * See {@link https://www.w3.org/TR/2017/REC-annotation-model-20170223/#refinement-of-selection
+ * | §4.2.9 Refinement of Selection} in the Web Annotation Data Model.
+ *
+ * @public
+ */
 export const supportRefinement: Plugin<any, any> =
   function supportRefinementPlugin<TScope, TMatch extends TScope>(
     next: MatcherCreator<TScope, TMatch>,
diff --git a/packages/selector/src/text/chunker.ts b/packages/selector/src/text/chunker.ts
new file mode 100644
index 0000000..4d959d2
--- /dev/null
+++ b/packages/selector/src/text/chunker.ts
@@ -0,0 +1,157 @@
+/**
+ * @license
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * Represents a piece of text in any kind of ‘file’.
+ *
+ * Its purpose is to enable generic algorithms to deal with text content of any
+ * type of ‘file’ that consists of many pieces of text (e.g. a DOM, PDF, …).
+ * Each Chunk represents one piece of text ({@link Chunk.data}). An object
+ * implementing this interface would typically have other attributes as well to
+ * map the chunk back to its position in the file (e.g. a Text node in the DOM).
+ *
+ * @typeParam TData - Piece of text, typically `string`
+ *
+ * @public
+ */
+export interface Chunk<TData> {
+  /**
+   * The piece of text this chunk represents.
+   */
+  readonly data: TData;
+  equals?(otherChunk: this): boolean;
+}
+
+/**
+ * Test two {@link Chunk}s for equality.
+ *
+ * Equality here means that both represent the same piece of text (i.e. at the
+ * same position) in the file. It compares using the custom {@link Chunk.equals}
+ * method if either chunk defines one, and falls back to checking the objects’
+ * identity (i.e. `chunk1 === chunk2`).
+ *
+ * @public
+ */
+export function chunkEquals(chunk1: Chunk<any>, chunk2: Chunk<any>): boolean {
+  if (chunk1.equals) return chunk1.equals(chunk2);
+  if (chunk2.equals) return chunk2.equals(chunk1);
+  return chunk1 === chunk2;
+}
+
+/**
+ * Points at a range of characters between two points inside {@link Chunk}s.
+ *
+ * Analogous to the DOM’s ({@link https://developer.mozilla.org/en-US/docs/Web/API/AbstractRange
+ * | Abstract}){@link https://developer.mozilla.org/en-US/docs/Web/API/Range |
+ * Range}. Each index expresses an offset inside the value of the corresponding
+ * {@link Chunk.data}, and can equal the length of that data in order to point
+ * to the position right after the chunk’s last character.
+ *
+ * @public
+ */
+export interface ChunkRange<TChunk extends Chunk<any>> {
+  startChunk: TChunk;
+  startIndex: number;
+  endChunk: TChunk;
+  endIndex: number;
+}
+
+/**
+ * Test two {@link ChunkRange}s for equality.
+ *
+ * Equality here means equality of each of their four properties (i.e.
+ * {@link startChunk}, {@link startIndex},
+ * {@link endChunk}, and {@link endIndex}).
+ * For the `startChunk`s and `endChunk`s, this function uses the custom
+ * {@link Chunk.equals} method if defined.
+ *
+ * Note that if the start/end of one range points at the end of a chunk, and the
+ * other to the start of a subsequent chunk, they are not considered equal, even
+ * though semantically they may be representing the same range of characters. To
+ * test for such semantic equivalence, ensure that both inputs are normalised:
+ * typically this means the range is shrunk to its narrowest equivalent, and (if
+ * it is empty) positioned at its first equivalent.
+ *
+ * @public
+ */
+export function chunkRangeEquals(
+  range1: ChunkRange<any>,
+  range2: ChunkRange<any>,
+): boolean {
+  return (
+    chunkEquals(range1.startChunk, range2.startChunk) &&
+    chunkEquals(range1.endChunk, range2.endChunk) &&
+    range1.startIndex === range2.startIndex &&
+    range1.endIndex === range2.endIndex
+  );
+}
+
+/**
+ * Presents the pieces of text contained in some underlying ‘file’ as a sequence
+ * of {@link Chunk}s.
+ *
+ * Rather than presenting a list of all pieces, the `Chunker` provides methods
+ * to walk through the file piece by piece. This permits implementations to read
+ * and convert the file to `Chunk`s lazily.
+ *
+ * For those familiar with the DOM APIs, it is similar to a NodeIterator (but
+ * unlike NodeIterator, it has no concept of being ‘before’ or ‘after’ a chunk).
+ *
+ * @typeParam TChunk - (sub)type of `Chunk` being used.
+ *
+ * @public
+ */
+export interface Chunker<TChunk extends Chunk<any>> {
+  /**
+   * The chunk currently being pointed at.
+   *
+   * Initially, this should normally be the first chunk in the file.
+   */
+  readonly currentChunk: TChunk;
+
+  /**
+   * Point {@link currentChunk} at the chunk following it, and return that chunk.
+   * If there are no chunks following it, keep `currentChunk` unchanged and
+   * return null.
+   */
+  nextChunk(): TChunk | null;
+
+  /**
+   * Point {@link currentChunk} at the chunk preceding it, and return that chunk.
+   * If there are no chunks preceding it, keep `currentChunk` unchanged and
+   * return null.
+   */
+  previousChunk(): TChunk | null;
+
+  /**
+   * Test if a given `chunk` is before the {@link currentChunk|current
+   * chunk}.
+   *
+   * Returns true if `chunk` is before `this.currentChunk`, false otherwise
+   * (i.e. if `chunk` follows it or is the current chunk).
+   *
+   * The given `chunk` need not necessarily be obtained from the same `Chunker`,
+   * but the chunkers would need to represent the same file. Otherwise behaviour
+   * is unspecified (an implementation might throw or just return `false`).
+   *
+   * @param chunk - A chunk, typically obtained from the same `Chunker`.
+   */
+  precedesCurrentChunk(chunk: TChunk): boolean;
+}
diff --git a/packages/selector/src/text/code-point-seeker.ts b/packages/selector/src/text/code-point-seeker.ts
new file mode 100644
index 0000000..5751f1e
--- /dev/null
+++ b/packages/selector/src/text/code-point-seeker.ts
@@ -0,0 +1,196 @@
+/**
+ * @license
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import type { Chunk } from './chunker';
+import type { Seeker } from './seeker';
+
+/**
+ * Seeks through text counting Unicode *code points* instead of *code units*.
+ *
+ * Javascript characters correspond to 16 bits *code units*, hence two such
+ * ‘characters’ might together constitute a single Unicode character (i.e. a
+ * *code point*). The {@link CodePointSeeker} allows to ignore this
+ * variable-length encoding, by counting code points instead.
+ *
+ * It is made to wrap a {@link Seeker} that counts code units (presumably a
+ * {@link TextSeeker}), which must be passed to its {@link constructor}.
+ *
+ * When reading from the `CodePointSeeker`, the returned values is not a string
+ * but an array of strings, each containing one code point (thus each having a
+ * `length` that is either 1 or 2).
+ *
+ * @public
+ */
+export class CodePointSeeker<TChunk extends Chunk<string>>
+  implements Seeker<TChunk, string[]> {
+  position = 0;
+
+  /**
+   *
+   * @param raw  The {@link Seeker} to wrap, which counts in code *units* (e.g.
+   * a {@link TextSeeker}). It should have {@link Seeker.position | position}
+   * `0` and its methods must no longer be used directly if the
+   * `CodePointSeeker`’s position is to remain correct.
+   */
+  constructor(public readonly raw: Seeker<TChunk>) {}
+
+  seekBy(length: number): void {
+    this.seekTo(this.position + length);
+  }
+
+  seekTo(target: number): void {
+    this._readOrSeekTo(false, target);
+  }
+
+  read(length: number, roundUp?: boolean): string[] {
+    return this.readTo(this.position + length, roundUp);
+  }
+
+  readTo(target: number, roundUp?: boolean): string[] {
+    return this._readOrSeekTo(true, target, roundUp);
+  }
+
+  get currentChunk(): TChunk {
+    return this.raw.currentChunk;
+  }
+
+  get offsetInChunk(): number {
+    return this.raw.offsetInChunk;
+  }
+
+  seekToChunk(target: TChunk, offset = 0): void {
+    this._readOrSeekToChunk(false, target, offset);
+  }
+
+  readToChunk(target: TChunk, offset = 0): string[] {
+    return this._readOrSeekToChunk(true, target, offset);
+  }
+
+  private _readOrSeekToChunk(
+    read: true,
+    target: TChunk,
+    offset?: number,
+  ): string[];
+  private _readOrSeekToChunk(
+    read: false,
+    target: TChunk,
+    offset?: number,
+  ): void;
+  private _readOrSeekToChunk(read: boolean, target: TChunk, offset = 0) {
+    const oldRawPosition = this.raw.position;
+
+    let s = this.raw.readToChunk(target, offset);
+
+    const movedForward = this.raw.position >= oldRawPosition;
+
+    if (movedForward && endsWithinCharacter(s)) {
+      this.raw.seekBy(-1);
+      s = s.slice(0, -1);
+    } else if (!movedForward && startsWithinCharacter(s)) {
+      this.raw.seekBy(1);
+      s = s.slice(1);
+    }
+
+    const result = [...s];
+
+    this.position = movedForward
+      ? this.position + result.length
+      : this.position - result.length;
+
+    if (read) return result;
+  }
+
+  private _readOrSeekTo(
+    read: true,
+    target: number,
+    roundUp?: boolean,
+  ): string[];
+  private _readOrSeekTo(read: false, target: number, roundUp?: boolean): void;
+  private _readOrSeekTo(
+    read: boolean,
+    target: number,
+    roundUp = false,
+  ): string[] | void {
+    let result: string[] = [];
+
+    if (this.position < target) {
+      let unpairedSurrogate = '';
+      let characters: string[] = [];
+      while (this.position < target) {
+        let s = unpairedSurrogate + this.raw.read(1, true);
+        if (endsWithinCharacter(s)) {
+          unpairedSurrogate = s.slice(-1); // consider this half-character part of the next string.
+          s = s.slice(0, -1);
+        } else {
+          unpairedSurrogate = '';
+        }
+        characters = [...s];
+        this.position += characters.length;
+        if (read) result = result.concat(characters);
+      }
+      if (unpairedSurrogate) this.raw.seekBy(-1); // align with the last complete character.
+      if (!roundUp && this.position > target) {
+        const overshootInCodePoints = this.position - target;
+        const overshootInCodeUnits = characters
+          .slice(-overshootInCodePoints)
+          .join('').length;
+        this.position -= overshootInCodePoints;
+        this.raw.seekBy(-overshootInCodeUnits);
+      }
+    } else {
+      // Nearly equal to the if-block, but moving backward in the text.
+      let unpairedSurrogate = '';
+      let characters: string[] = [];
+      while (this.position > target) {
+        let s = this.raw.read(-1, true) + unpairedSurrogate;
+        if (startsWithinCharacter(s)) {
+          unpairedSurrogate = s[0];
+          s = s.slice(1);
+        } else {
+          unpairedSurrogate = '';
+        }
+        characters = [...s];
+        this.position -= characters.length;
+        if (read) result = characters.concat(result);
+      }
+      if (unpairedSurrogate) this.raw.seekBy(1);
+      if (!roundUp && this.position < target) {
+        const overshootInCodePoints = target - this.position;
+        const overshootInCodeUnits = characters
+          .slice(0, overshootInCodePoints)
+          .join('').length;
+        this.position += overshootInCodePoints;
+        this.raw.seekBy(overshootInCodeUnits);
+      }
+    }
+
+    if (read) return result;
+  }
+}
+
+function endsWithinCharacter(s: string) {
+  const codeUnit = s.charCodeAt(s.length - 1);
+  return 0xd800 <= codeUnit && codeUnit <= 0xdbff;
+}
+
+function startsWithinCharacter(s: string) {
+  const codeUnit = s.charCodeAt(0);
+  return 0xdc00 <= codeUnit && codeUnit <= 0xdfff;
+}
diff --git a/packages/selector/src/text/describe-text-position.ts b/packages/selector/src/text/describe-text-position.ts
new file mode 100644
index 0000000..a5a7cd8
--- /dev/null
+++ b/packages/selector/src/text/describe-text-position.ts
@@ -0,0 +1,61 @@
+/**
+ * @license
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import type { TextPositionSelector } from '../types';
+import type { Chunk, Chunker, ChunkRange } from './chunker';
+import { CodePointSeeker } from './code-point-seeker';
+import { TextSeeker } from './seeker';
+
+/**
+ * Returns a {@link TextPositionSelector} that points at the target text within
+ * the given scope.
+ *
+ * This is an abstract implementation of the function’s logic, which expects a
+ * generic {@link Chunker} to represent the text, and a {@link ChunkRange} to
+ * represent the target.
+ *
+ * See {@link dom.describeTextPosition} for a wrapper around
+ * this implementation which applies it to the text of an HTML DOM.
+ *
+ * @param target - The range of characters that the selector should describe
+ * @param scope - The text, presented as a {@link Chunker}, which contains the
+ * target range, and relative to which its position will be measured
+ * @returns The {@link TextPositionSelector} that describes `target` relative
+ * to `scope`
+ *
+ * @public
+ */
+export async function describeTextPosition<TChunk extends Chunk<string>>(
+  target: ChunkRange<TChunk>,
+  scope: Chunker<TChunk>,
+): Promise<TextPositionSelector> {
+  const codeUnitSeeker = new TextSeeker(scope);
+  const codePointSeeker = new CodePointSeeker(codeUnitSeeker);
+
+  codePointSeeker.seekToChunk(target.startChunk, target.startIndex);
+  const start = codePointSeeker.position;
+  codePointSeeker.seekToChunk(target.endChunk, target.endIndex);
+  const end = codePointSeeker.position;
+  return {
+    type: 'TextPositionSelector',
+    start,
+    end,
+  };
+}
diff --git a/packages/selector/src/text/describe-text-quote.ts b/packages/selector/src/text/describe-text-quote.ts
new file mode 100644
index 0000000..54b5e01
--- /dev/null
+++ b/packages/selector/src/text/describe-text-quote.ts
@@ -0,0 +1,298 @@
+/**
+ * @license
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import type { TextQuoteSelector } from '../types';
+import type { Chunk, Chunker, ChunkRange } from './chunker';
+import { chunkRangeEquals } from './chunker';
+import type { RelativeSeeker } from './seeker';
+import { TextSeeker } from './seeker';
+import { textQuoteSelectorMatcher } from '.';
+
+/**
+ * @public
+ */
+export interface DescribeTextQuoteOptions {
+  /**
+   * Keep prefix and suffix to the minimum that is necessary to disambiguate
+   * the quote. Use only if robustness against text variations is not required.
+   */
+  minimalContext?: boolean;
+
+  /**
+   * Add prefix and suffix to quotes below this length, such that the total of
+   * `prefix + exact + suffix` is at least this length.
+   */
+  minimumQuoteLength?: number;
+
+  /**
+   * When attempting to find a whitespace to make the prefix/suffix start/end
+   * (resp.) at a word boundary, give up after this number of characters.
+   */
+  maxWordLength?: number;
+}
+
+/**
+ * Returns a {@link TextQuoteSelector} that points at the target quote in the
+ * given text.
+ *
+ * The selector will contain the exact target quote. In case this quote appears
+ * multiple times in the text, sufficient context around the quote will be
+ * included in the selector’s `prefix` and `suffix` attributes to disambiguate.
+ * By default, more prefix and suffix are included than strictly required; both
+ * in order to be robust against slight modifications, and in an attempt to not
+ * end halfway a word (mainly for human readability).
+ *
+ * This is an abstract implementation of the function’s logic, which expects a
+ * generic {@link Chunker} to represent the text, and a {@link ChunkRange} to
+ * represent the target.
+ *
+ * See {@link dom.describeTextQuote} for a wrapper around this
+ * implementation which applies it to the text of an HTML DOM.
+ *
+ * @param target - The range of characters that the selector should describe
+ * @param scope - The text containing the target range; or, more accurately, a
+ * function that produces {@link Chunker}s corresponding to this text.
+ * @param options - Options to fine-tune the function’s behaviour.
+ * @returns The {@link TextQuoteSelector} that describes `target`.
+ *
+ * @public
+ */
+export async function describeTextQuote<TChunk extends Chunk<string>>(
+  target: ChunkRange<TChunk>,
+  scope: () => Chunker<TChunk>,
+  options: DescribeTextQuoteOptions = {},
+): Promise<TextQuoteSelector> {
+  const {
+    minimalContext = false,
+    minimumQuoteLength = 0,
+    maxWordLength = 50,
+  } = options;
+
+  // Create a seeker to read the target quote and the context around it.
+  // TODO Possible optimisation: as it need not be an AbsoluteSeeker, a
+  // different implementation could provide direct ‘jump’ access in seekToChunk
+  // (the scope’s Chunker would of course also have to support this).
+  const seekerAtTarget = new TextSeeker(scope());
+
+  // Create a second seeker so that we will be able to simultaneously read
+  // characters near both the target and an unintended match, if we find any.
+  const seekerAtUnintendedMatch = new TextSeeker(scope());
+
+  // Read the target’s exact text.
+  seekerAtTarget.seekToChunk(target.startChunk, target.startIndex);
+  const exact = seekerAtTarget.readToChunk(target.endChunk, target.endIndex);
+
+  // Start with an empty prefix and suffix.
+  let prefix = '';
+  let suffix = '';
+
+  // If the quote is below the given minimum length, add some prefix & suffix.
+  const currentQuoteLength = () => prefix.length + exact.length + suffix.length;
+  if (currentQuoteLength() < minimumQuoteLength) {
+    // Expand the prefix, but only to reach halfway towards the desired length.
+    seekerAtTarget.seekToChunk(
+      target.startChunk,
+      target.startIndex - prefix.length,
+    );
+    const length = Math.floor((minimumQuoteLength - currentQuoteLength()) / 2);
+    prefix = seekerAtTarget.read(-length, false, true) + prefix;
+
+    // If needed, expand the suffix to achieve the minimum length.
+    if (currentQuoteLength() < minimumQuoteLength) {
+      seekerAtTarget.seekToChunk(
+        target.endChunk,
+        target.endIndex + suffix.length,
+      );
+      const length = minimumQuoteLength - currentQuoteLength();
+      suffix = suffix + seekerAtTarget.read(length, false, true);
+
+      // We might have to expand the prefix again (if at the end of the scope).
+      if (currentQuoteLength() < minimumQuoteLength) {
+        seekerAtTarget.seekToChunk(
+          target.startChunk,
+          target.startIndex - prefix.length,
+        );
+        const length = minimumQuoteLength - currentQuoteLength();
+        prefix = seekerAtTarget.read(-length, false, true) + prefix;
+      }
+    }
+  }
+
+  // Expand prefix & suffix to avoid them ending somewhere halfway in a word.
+  if (!minimalContext) {
+    seekerAtTarget.seekToChunk(
+      target.startChunk,
+      target.startIndex - prefix.length,
+    );
+    prefix = readUntilWhitespace(seekerAtTarget, maxWordLength, true) + prefix;
+    seekerAtTarget.seekToChunk(
+      target.endChunk,
+      target.endIndex + suffix.length,
+    );
+    suffix = suffix + readUntilWhitespace(seekerAtTarget, maxWordLength, false);
+  }
+
+  // Search for matches of the quote using the current prefix and suffix. At
+  // each unintended match we encounter, we extend the prefix or suffix to
+  // ensure it will no longer match.
+  while (true) {
+    const tentativeSelector: TextQuoteSelector = {
+      type: 'TextQuoteSelector',
+      exact,
+      prefix,
+      suffix,
+    };
+
+    const matches = textQuoteSelectorMatcher(tentativeSelector)(scope());
+    let nextMatch = await matches.next();
+
+    // If this match is the intended one, no need to act.
+    // XXX This test is fragile: nextMatch and target are assumed to be normalised.
+    if (!nextMatch.done && chunkRangeEquals(nextMatch.value, target)) {
+      nextMatch = await matches.next();
+    }
+
+    // If there are no more unintended matches, our selector is unambiguous!
+    if (nextMatch.done) return tentativeSelector;
+
+    // Possible optimisation: A subsequent search could safely skip the part we
+    // already processed, instead of starting from the beginning again. But we’d
+    // need the matcher to start at the seeker’s position, instead of searching
+    // in the whole current chunk. Then we could just seek back to just after
+    // the start of the prefix: seeker.seekBy(-prefix.length + 1); (don’t forget
+    // to also correct for any changes in the prefix we will make below)
+
+    // We’ll have to add more prefix/suffix to disqualify this unintended match.
+    const unintendedMatch = nextMatch.value;
+
+    // Count how many characters we’d need as a prefix to disqualify this match.
+    seekerAtTarget.seekToChunk(
+      target.startChunk,
+      target.startIndex - prefix.length,
+    );
+    seekerAtUnintendedMatch.seekToChunk(
+      unintendedMatch.startChunk,
+      unintendedMatch.startIndex - prefix.length,
+    );
+    let extraPrefix = readUntilDifferent(
+      seekerAtTarget,
+      seekerAtUnintendedMatch,
+      true,
+    );
+    if (extraPrefix !== undefined && !minimalContext)
+      extraPrefix =
+        readUntilWhitespace(seekerAtTarget, maxWordLength, true) + extraPrefix;
+
+    // Count how many characters we’d need as a suffix to disqualify this match.
+    seekerAtTarget.seekToChunk(
+      target.endChunk,
+      target.endIndex + suffix.length,
+    );
+    seekerAtUnintendedMatch.seekToChunk(
+      unintendedMatch.endChunk,
+      unintendedMatch.endIndex + suffix.length,
+    );
+    let extraSuffix = readUntilDifferent(
+      seekerAtTarget,
+      seekerAtUnintendedMatch,
+      false,
+    );
+    if (extraSuffix !== undefined && !minimalContext)
+      extraSuffix =
+        extraSuffix + readUntilWhitespace(seekerAtTarget, maxWordLength, false);
+
+    if (minimalContext) {
+      // Use either the prefix or suffix, whichever is shortest.
+      if (
+        extraPrefix !== undefined &&
+        (extraSuffix === undefined || extraPrefix.length <= extraSuffix.length)
+      ) {
+        prefix = extraPrefix + prefix;
+      } else if (extraSuffix !== undefined) {
+        suffix = suffix + extraSuffix;
+      } else {
+        throw new Error(
+          'Target cannot be disambiguated; how could that have happened‽',
+        );
+      }
+    } else {
+      // For redundancy, expand both prefix and suffix.
+      if (extraPrefix !== undefined) prefix = extraPrefix + prefix;
+      if (extraSuffix !== undefined) suffix = suffix + extraSuffix;
+    }
+  }
+}
+
+function readUntilDifferent(
+  seeker1: RelativeSeeker,
+  seeker2: RelativeSeeker,
+  reverse: boolean,
+): string | undefined {
+  let result = '';
+  while (true) {
+    let nextCharacter: string;
+    try {
+      nextCharacter = seeker1.read(reverse ? -1 : 1);
+    } catch (err) {
+      return undefined; // Start/end of text reached: cannot expand result.
+    }
+    result = reverse ? nextCharacter + result : result + nextCharacter;
+
+    // Check if the newly added character makes the result differ from the second seeker.
+    let comparisonCharacter: string | undefined;
+    try {
+      comparisonCharacter = seeker2.read(reverse ? -1 : 1);
+    } catch (err) {
+      // A RangeError would merely mean seeker2 is exhausted.
+      if (!(err instanceof RangeError)) throw err;
+    }
+    if (nextCharacter !== comparisonCharacter) return result;
+  }
+}
+
+function readUntilWhitespace(
+  seeker: RelativeSeeker,
+  limit = Infinity,
+  reverse = false,
+): string {
+  let result = '';
+  while (result.length < limit) {
+    let nextCharacter: string;
+    try {
+      nextCharacter = seeker.read(reverse ? -1 : 1);
+    } catch (err) {
+      if (!(err instanceof RangeError)) throw err;
+      break; // End/start of text reached.
+    }
+
+    // Stop if we reached whitespace.
+    if (isWhitespace(nextCharacter)) {
+      seeker.seekBy(reverse ? 1 : -1); // ‘undo’ the last read.
+      break;
+    }
+
+    result = reverse ? nextCharacter + result : result + nextCharacter;
+  }
+  return result;
+}
+
+function isWhitespace(s: string): boolean {
+  return /^\s+$/.test(s);
+}
diff --git a/packages/dom/src/types/cartesian.d.ts b/packages/selector/src/text/index.ts
similarity index 81%
rename from packages/dom/src/types/cartesian.d.ts
rename to packages/selector/src/text/index.ts
index 9578e84..0261823 100644
--- a/packages/dom/src/types/cartesian.d.ts
+++ b/packages/selector/src/text/index.ts
@@ -18,8 +18,8 @@
  * under the License.
  */
 
-declare module 'cartesian' {
-  export default function cartesian<T>(
-    list: Array<Array<T>> | { [k: string]: Array<T> },
-  ): Array<Array<T>>;
-}
+export * from './describe-text-quote';
+export * from './match-text-quote';
+export * from './describe-text-position';
+export * from './match-text-position';
+export * from './chunker';
diff --git a/packages/selector/src/text/match-text-position.ts b/packages/selector/src/text/match-text-position.ts
new file mode 100644
index 0000000..6f2f8be
--- /dev/null
+++ b/packages/selector/src/text/match-text-position.ts
@@ -0,0 +1,76 @@
+/**
+ * @license
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import type { TextPositionSelector } from '../types';
+import type { Chunk, ChunkRange, Chunker } from './chunker';
+import { CodePointSeeker } from './code-point-seeker';
+import { TextSeeker } from './seeker';
+
+/**
+ * Find the range of text corresponding to the given {@link TextPositionSelector}.
+ *
+ * This is an abstract implementation of the function’s logic, which expects a
+ * generic {@link Chunker} to represent the text, and returns an (async)
+ * generator producing a single {@link ChunkRange} to represent the match.
+ * (unlike e.g. TextQuoteSelector, it cannot result in multiple matches).
+ *
+ * See {@link dom.createTextPositionSelectorMatcher} for a
+ * wrapper around this implementation which applies it to the text of an HTML
+ * DOM.
+ *
+ * The function is curried, taking first the selector and then the text.
+ *
+ * @example
+ * ```
+ * const selector = { type: 'TextPositionSelector', start: 702, end: 736 };
+ * const matches = textPositionSelectorMatcher(selector)(textChunks);
+ * const match = (await matches.next()).value;
+ * console.log(match);
+ * // ⇒ { startChunk: { … }, startIndex: 64, endChunk: { … }, endIndex: 98 }
+ * ```
+ *
+ * @param selector - the {@link TextPositionSelector} to be anchored
+ * @returns a {@link Matcher} function that applies `selector` to a given text
+ *
+ * @public
+ */
+export function textPositionSelectorMatcher(
+  selector: TextPositionSelector,
+): <TChunk extends Chunk<any>>(
+  scope: Chunker<TChunk>,
+) => AsyncGenerator<ChunkRange<TChunk>, void, void> {
+  const { start, end } = selector;
+
+  return async function* matchAll<TChunk extends Chunk<string>>(
+    textChunks: Chunker<TChunk>,
+  ) {
+    const codeUnitSeeker = new TextSeeker(textChunks);
+    const codePointSeeker = new CodePointSeeker(codeUnitSeeker);
+
+    codePointSeeker.seekTo(start);
+    const startChunk = codeUnitSeeker.currentChunk;
+    const startIndex = codeUnitSeeker.offsetInChunk;
+    codePointSeeker.seekTo(end);
+    const endChunk = codeUnitSeeker.currentChunk;
+    const endIndex = codeUnitSeeker.offsetInChunk;
+
+    yield { startChunk, startIndex, endChunk, endIndex };
+  };
+}
diff --git a/packages/selector/src/text/match-text-quote.ts b/packages/selector/src/text/match-text-quote.ts
new file mode 100644
index 0000000..0531369
--- /dev/null
+++ b/packages/selector/src/text/match-text-quote.ts
@@ -0,0 +1,208 @@
+/**
+ * @license
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import type { TextQuoteSelector } from '../types';
+import type { Chunk, Chunker, ChunkRange } from './chunker';
+
+/**
+ * Find occurrences in a text matching the given {@link TextQuoteSelector}.
+ *
+ * This performs an exact search the selector’s quote (including prefix and
+ * suffix) within the given text.
+ *
+ * Note the match is based on strict character-by-character equivalence, i.e.
+ * it is sensitive to whitespace, capitalisation, etc.
+ *
+ * This is an abstract implementation of the function’s logic, which expects a
+ * generic {@link Chunker} to represent the text, and returns an (async)
+ * generator of {@link ChunkRange}s to represent the matches.
+ *
+ * See {@link dom.createTextQuoteSelectorMatcher} for a
+ * wrapper around this implementation which applies it to the text of an HTML
+ * DOM.
+ *
+ * The function is curried, taking first the selector and then the text.
+ *
+ * As there may be multiple matches for a given selector (when its prefix and
+ * suffix attributes are not sufficient to disambiguate it), the matcher will
+ * return an (async) generator that produces each match in the order they are
+ * found in the text.
+ *
+ * @example
+ * ```
+ * const selector = { type: 'TextQuoteSelector', exact: 'banana' };
+ * const matches = textQuoteSelectorMatcher(selector)(textChunks);
+ * for await (match of matches) console.log(match);
+ * // ⇒ { startChunk: { … }, startIndex: 187, endChunk: { … }, endIndex: 193 }
+ * // ⇒ { startChunk: { … }, startIndex: 631, endChunk: { … }, endIndex: 637 }
+ * ```
+ *
+ * @param selector - The {@link TextQuoteSelector} to be anchored
+ * @returns a {@link Matcher} function that applies `selector` to a given text
+ *
+ * @public
+ */
+export function textQuoteSelectorMatcher(
+  selector: TextQuoteSelector,
+): <TChunk extends Chunk<any>>(
+  scope: Chunker<TChunk>,
+) => AsyncGenerator<ChunkRange<TChunk>, void, void> {
+  return async function* matchAll<TChunk extends Chunk<string>>(
+    textChunks: Chunker<TChunk>,
+  ) {
+    const exact = selector.exact;
+    const prefix = selector.prefix || '';
+    const suffix = selector.suffix || '';
+    const searchPattern = prefix + exact + suffix;
+
+    // The code below essentially just performs string.indexOf(searchPattern),
+    // but on a string that is chopped up in multiple chunks. It runs a loop
+    // containing three steps:
+    // 1. Continue checking any partial matches from the previous chunk(s).
+    // 2. Try find the whole pattern in the chunk (possibly multiple times).
+    // 3. Check if this chunk ends with a partial match (or even multiple partial matches).
+
+    interface PartialMatch {
+      startChunk?: TChunk;
+      startIndex?: number;
+      endChunk?: TChunk;
+      endIndex?: number;
+      charactersMatched: number;
+    }
+    let partialMatches: PartialMatch[] = [];
+
+    let isFirstChunk = true;
+    do {
+      const chunk = textChunks.currentChunk;
+      const chunkValue = chunk.data;
+
+      // 1. Continue checking any partial matches from the previous chunk(s).
+      const remainingPartialMatches: typeof partialMatches = [];
+      for (const partialMatch of partialMatches) {
+        const charactersMatched = partialMatch.charactersMatched;
+
+        // If the current chunk contains the start and/or end of the match, record these.
+        if (partialMatch.endChunk === undefined) {
+          const charactersUntilMatchEnd =
+            prefix.length + exact.length - charactersMatched;
+          if (charactersUntilMatchEnd <= chunkValue.length) {
+            partialMatch.endChunk = chunk;
+            partialMatch.endIndex = charactersUntilMatchEnd;
+          }
+        }
+        if (partialMatch.startChunk === undefined) {
+          const charactersUntilMatchStart = prefix.length - charactersMatched;
+          if (
+            charactersUntilMatchStart < chunkValue.length ||
+            partialMatch.endChunk !== undefined // handles an edge case: an empty quote at the end of a chunk.
+          ) {
+            partialMatch.startChunk = chunk;
+            partialMatch.startIndex = charactersUntilMatchStart;
+          }
+        }
+
+        const charactersUntilSuffixEnd =
+          searchPattern.length - charactersMatched;
+        if (charactersUntilSuffixEnd <= chunkValue.length) {
+          if (
+            chunkValue.startsWith(searchPattern.substring(charactersMatched))
+          ) {
+            yield partialMatch as ChunkRange<TChunk>; // all fields are certainly defined now.
+          }
+        } else if (
+          chunkValue ===
+          searchPattern.substring(
+            charactersMatched,
+            charactersMatched + chunkValue.length,
+          )
+        ) {
+          // The chunk is too short to complete the match; comparison has to be completed in subsequent chunks.
+          partialMatch.charactersMatched += chunkValue.length;
+          remainingPartialMatches.push(partialMatch);
+        }
+      }
+      partialMatches = remainingPartialMatches;
+
+      // 2. Try find the whole pattern in the chunk (possibly multiple times).
+      if (searchPattern.length <= chunkValue.length) {
+        let fromIndex = 0;
+        while (fromIndex <= chunkValue.length) {
+          const patternStartIndex = chunkValue.indexOf(
+            searchPattern,
+            fromIndex,
+          );
+          if (patternStartIndex === -1) break;
+          fromIndex = patternStartIndex + 1;
+
+          // Handle edge case: an empty searchPattern would already have been yielded at the end of the last chunk.
+          if (
+            patternStartIndex === 0 &&
+            searchPattern.length === 0 &&
+            !isFirstChunk
+          )
+            continue;
+
+          yield {
+            startChunk: chunk,
+            startIndex: patternStartIndex + prefix.length,
+            endChunk: chunk,
+            endIndex: patternStartIndex + prefix.length + exact.length,
+          };
+        }
+      }
+
+      // 3. Check if this chunk ends with a partial match (or even multiple partial matches).
+      let newPartialMatches: number[] = [];
+      const searchStartPoint = Math.max(
+        chunkValue.length - searchPattern.length + 1,
+        0,
+      );
+      for (let i = searchStartPoint; i < chunkValue.length; i++) {
+        const character = chunkValue[i];
+        newPartialMatches = newPartialMatches.filter(
+          (partialMatchStartIndex) =>
+            character === searchPattern[i - partialMatchStartIndex],
+        );
+        if (character === searchPattern[0]) newPartialMatches.push(i);
+      }
+      for (const partialMatchStartIndex of newPartialMatches) {
+        const charactersMatched = chunkValue.length - partialMatchStartIndex;
+        const partialMatch: PartialMatch = {
+          charactersMatched,
+        };
+        if (charactersMatched >= prefix.length + exact.length) {
+          partialMatch.endChunk = chunk;
+          partialMatch.endIndex =
+            partialMatchStartIndex + prefix.length + exact.length;
+        }
+        if (
+          charactersMatched > prefix.length ||
+          partialMatch.endChunk !== undefined // handles an edge case: an empty quote at the end of a chunk.
+        ) {
+          partialMatch.startChunk = chunk;
+          partialMatch.startIndex = partialMatchStartIndex + prefix.length;
+        }
+        partialMatches.push(partialMatch);
+      }
+
+      isFirstChunk = false;
+    } while (textChunks.nextChunk() !== null);
+  };
+}
diff --git a/packages/selector/src/text/seeker.ts b/packages/selector/src/text/seeker.ts
new file mode 100644
index 0000000..b8246c6
--- /dev/null
+++ b/packages/selector/src/text/seeker.ts
@@ -0,0 +1,415 @@
+/**
+ * @license
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import type { Chunk, Chunker } from './chunker';
+import { chunkEquals } from './chunker';
+
+const E_END = 'Iterator exhausted before seek ended.';
+
+/**
+ * Abstraction to seek (jump) or read to a position inside a ‘file’ consisting of a
+ * sequence of data chunks.
+ *
+ * This interface is a combination of three interfaces in one: for seeking to a
+ * relative position, an absolute position, or a specific chunk. These three are
+ * defined separately for clarity and flexibility, but normally used together.
+ *
+ * A Seeker internally maintains a pointer to the chunk it is currently ‘in’ and
+ * the offset position within that chunk.
+ *
+ * @typeParam TChunk - Type of chunks the file consists of.
+ * @typeParam TData - Type of data this seeker’s read methods will return (not
+ * necessarily the same as the `TData` parameter of {@link Chunk}, see e.g.
+ * {@link CodePointSeeker})
+ *
+ * @public
+ */
+export interface Seeker<
+  TChunk extends Chunk<any>,
+  TData extends Iterable<any> = string
+>
+  extends RelativeSeeker<TData>,
+    AbsoluteSeeker<TData>,
+    ChunkSeeker<TChunk, TData> {}
+
+/**
+ * Seeks/reads by a given number of characters.
+ *
+ * @public
+ */
+export interface RelativeSeeker<TData extends Iterable<any> = string> {
+  /**
+   * Move forward or backward by a number of characters.
+   *
+   * @param length - The number of characters to pass. A negative number moves
+   * backwards in the file.
+   * @throws RangeError if there are not enough characters in the file. The
+   * pointer is left at the end/start of the file.
+   */
+  seekBy(length: number): void;
+
+  /**
+   * Read forward or backward by a number of characters.
+   *
+   * Equal to {@link seekBy}, but returning the characters passed.
+   *
+   * @param length - The number of characters to read. A negative number moves
+   * backwards in the file.
+   * @param roundUp - If true, then, after reading the given number of
+   * characters, read further until the end (or start) of the current chunk.
+   * @param lessIsFine - If true, and there are not enough characters in the
+   * file, return the result so far instead of throwing an error.
+   * @returns The characters passed (in their normal order, even when moving
+   * backwards)
+   * @throws RangeError if there are not enough characters in the file (unless
+   * `lessIsFine` is true). The pointer is left at the end/start of the file.
+   */
+  read(length?: number, roundUp?: boolean, lessIsFine?: boolean): TData;
+}
+
+/**
+ * Seek/read to absolute positions in the file.
+ *
+ * @public
+ */
+export interface AbsoluteSeeker<TData extends Iterable<any> = string> {
+  /**
+   * The current position in the file in terms of character count: i.e. the
+   * number of characters before the place currently being pointed at.
+   */
+  readonly position: number;
+
+  /**
+   * Move to the given position in the file.
+   *
+   * @param target - The position to end up at.
+   * @throws RangeError if the given position is beyond the end/start of the
+   * file. The pointer is left at the end/start of the file.
+   */
+  seekTo(target: number): void;
+
+  /**
+   * Read forward or backward from the current to the given position in the
+   * file, returning the characters that have been passed.
+   *
+   * Equal to {@link seekTo}, but returning the characters passed.
+   *
+   * @param target - The position to end up at.
+   * @param roundUp - If true, then, after reading to the target position, read
+   * further until the end (or start) of the current chunk.
+   * @returns The characters passed (in their normal order, even when moving
+   * backwards)
+   * @throws RangeError if the given position is beyond the end/start of the
+   * file. The pointer is left at the end/start of the file.
+   */
+  readTo(target: number, roundUp?: boolean): TData;
+}
+
+/**
+ * Seek/read to (and within) specfic chunks the file consists of; and access the
+ * chunk and offset in that chunk corresponding to the current position.
+ *
+ * Note that all offset numbers in this interface are representing units of the
+ * {@link Chunk.data | data type of `TChunk`}; which might differ from that of
+ * `TData`.
+ *
+ * @public
+ */
+export interface ChunkSeeker<
+  TChunk extends Chunk<any>,
+  TData extends Iterable<any> = string
+> {
+  /**
+   * The chunk containing the current position.
+   *
+   * When the position falls at the edge between two chunks, `currentChunk` is
+   * always the later one (thus {@link offsetInChunk} would be zero). Note that
+   * an empty chunk (for which position zero is at both its edges) can
+   * hence never be the current chunk unless it is the last chunk in the file.
+   */
+  readonly currentChunk: TChunk;
+
+  /**
+   * The offset inside `currentChunk` corresponding to the current position.
+   * Can be between zero and the length of the chunk (inclusive; but it could
+   * equal the length of the chunk only if currentChunk is the last chunk).
+   */
+  readonly offsetInChunk: number;
+
+  /**
+   * Move to the start of a given chunk, or to an offset relative to that.
+   *
+   * @param chunk - The chunk of the file to move to.
+   * @param offset - The offset to move to, relative to the start of `chunk`.
+   * Defaults to zero.
+   * @throws RangeError if the given chunk is not found in the file.
+   */
+  seekToChunk(chunk: TChunk, offset?: number): void;
+
+  /**
+   * Read to the start of a given chunk, or to an offset relative to that.
+   *
+   * Equal to {@link seekToChunk}, but returning the characters passed.
+   *
+   * @param chunk - The chunk of the file to move to.
+   * @param offset - The offset to move to, relative to the start of `chunk`.
+   * Defaults to zero.
+   * @returns The characters passed (in their normal order, even when moving
+   * backwards)
+   * @throws RangeError if the given chunk is not found in the file.
+   */
+  readToChunk(chunk: TChunk, offset?: number): TData;
+}
+
+/**
+ * A TextSeeker is constructed around a {@link Chunker}, to let it be treated as
+ * a continuous sequence of characters.
+ *
+ * Seeking to a given numeric position will cause a `TextSeeker` to pull chunks
+ * from the underlying `Chunker`, counting their lengths until the requested
+ * position is reached. `Chunks` are not stored but simply read again when
+ * seeking backwards.
+ *
+ * The `Chunker` is presumed to read an unchanging file. If a chunk’s length
+ * would change while seeking, a TextSeeker’s absolute positioning would be
+ * incorrect.
+ *
+ * See {@link CodePointSeeker} for a {@link Seeker} that counts Unicode *code
+ * points* instead of Javascript’s ‘normal’ characters.
+ *
+ * @public
+ */
+export class TextSeeker<TChunk extends Chunk<string>>
+  implements Seeker<TChunk> {
+  // The chunk containing our current text position.
+  get currentChunk(): TChunk {
+    return this.chunker.currentChunk;
+  }
+
+  // The index of the first character of the current chunk inside the text.
+  private currentChunkPosition = 0;
+
+  // The position inside the chunk where the last seek ended up.
+  offsetInChunk = 0;
+
+  // The current text position (measured in code units)
+  get position(): number {
+    return this.currentChunkPosition + this.offsetInChunk;
+  }
+
+  constructor(protected chunker: Chunker<TChunk>) {
+    // Walk to the start of the first non-empty chunk inside the scope.
+    this.seekTo(0);
+  }
+
+  read(length: number, roundUp = false, lessIsFine = false): string {
+    return this._readOrSeekTo(
+      true,
+      this.position + length,
+      roundUp,
+      lessIsFine,
+    );
+  }
+
+  readTo(target: number, roundUp = false): string {
+    return this._readOrSeekTo(true, target, roundUp);
+  }
+
+  seekBy(length: number): void {
+    this.seekTo(this.position + length);
+  }
+
+  seekTo(target: number): void {
+    this._readOrSeekTo(false, target);
+  }
+
+  seekToChunk(target: TChunk, offset = 0): void {
+    this._readOrSeekToChunk(false, target, offset);
+  }
+
+  readToChunk(target: TChunk, offset = 0): string {
+    return this._readOrSeekToChunk(true, target, offset);
+  }
+
+  private _readOrSeekToChunk(
+    read: true,
+    target: TChunk,
+    offset?: number,
+  ): string;
+  private _readOrSeekToChunk(
+    read: false,
+    target: TChunk,
+    offset?: number,
+  ): void;
+  private _readOrSeekToChunk(
+    read: boolean,
+    target: TChunk,
+    offset = 0,
+  ): string | void {
+    const oldPosition = this.position;
+    let result = '';
+
+    // Walk to the requested chunk.
+    if (!this.chunker.precedesCurrentChunk(target)) {
+      // Search forwards.
+      while (!chunkEquals(this.currentChunk, target)) {
+        const [data, nextChunk] = this._readToNextChunk();
+        if (read) result += data;
+        if (nextChunk === null) throw new RangeError(E_END);
+      }
+    } else {
+      // Search backwards.
+      while (!chunkEquals(this.currentChunk, target)) {
+        const [data, previousChunk] = this._readToPreviousChunk();
+        if (read) result = data + result;
+        if (previousChunk === null) throw new RangeError(E_END);
+      }
+    }
+
+    // Now we know where the chunk is, walk to the requested offset.
+    // Note we might have started inside the chunk, and the offset could even
+    // point at a position before or after the chunk.
+    const targetPosition = this.currentChunkPosition + offset;
+    if (!read) {
+      this.seekTo(targetPosition);
+    } else {
+      if (targetPosition >= this.position) {
+        // Read further until the target.
+        result += this.readTo(targetPosition);
+      } else if (targetPosition >= oldPosition) {
+        // We passed by our target position: step back.
+        this.seekTo(targetPosition);
+        result = result.slice(0, targetPosition - oldPosition);
+      } else {
+        // The target precedes our starting position: read backwards from there.
+        this.seekTo(oldPosition);
+        result = this.readTo(targetPosition);
+      }
+      return result;
+    }
+  }
+
+  private _readOrSeekTo(
+    read: true,
+    target: number,
+    roundUp?: boolean,
+    lessIsFine?: boolean,
+  ): string;
+  private _readOrSeekTo(
+    read: false,
+    target: number,
+    roundUp?: boolean,
+    lessIsFine?: boolean,
+  ): void;
+  private _readOrSeekTo(
+    read: boolean,
+    target: number,
+    roundUp = false,
+    lessIsFine = false,
+  ): string | void {
+    let result = '';
+
+    if (this.position <= target) {
+      while (true) {
+        const endOfChunk =
+          this.currentChunkPosition + this.currentChunk.data.length;
+        if (endOfChunk <= target) {
+          // The target is beyond the current chunk.
+          // (we use < not ≤: if the target is *at* the end of the chunk, possibly
+          // because the current chunk is empty, we prefer to take the next chunk)
+
+          const [data, nextChunk] = this._readToNextChunk();
+          if (read) result += data;
+          if (nextChunk === null) {
+            if (this.position === target || lessIsFine) break;
+            else throw new RangeError(E_END);
+          }
+        } else {
+          // The target is within the current chunk.
+          const newOffset = roundUp
+            ? this.currentChunk.data.length
+            : target - this.currentChunkPosition;
+          if (read)
+            result += this.currentChunk.data.substring(
+              this.offsetInChunk,
+              newOffset,
+            );
+          this.offsetInChunk = newOffset;
+
+          // If we finish end at the end of the chunk, seek to the start of the next non-empty node.
+          // (TODO decide: should we keep this guarantee of not finishing at the end of a chunk?)
+          if (roundUp) this.seekBy(0);
+
+          break;
+        }
+      }
+    } else {
+      // Similar to the if-block, but moving backward in the text.
+      while (this.position > target) {
+        if (this.currentChunkPosition <= target) {
+          // The target is within the current chunk.
+          const newOffset = roundUp ? 0 : target - this.currentChunkPosition;
+          if (read)
+            result =
+              this.currentChunk.data.substring(newOffset, this.offsetInChunk) +
+              result;
+          this.offsetInChunk = newOffset;
+          break;
+        } else {
+          const [data, previousChunk] = this._readToPreviousChunk();
+          if (read) result = data + result;
+          if (previousChunk === null) {
+            if (lessIsFine) break;
+            else throw new RangeError(E_END);
+          }
+        }
+      }
+    }
+
+    if (read) return result;
+  }
+
+  // Read to the start of the next chunk, if any; otherwise to the end of the current chunk.
+  _readToNextChunk(): [string, TChunk | null] {
+    const data = this.currentChunk.data.substring(this.offsetInChunk);
+    const chunkLength = this.currentChunk.data.length;
+    const nextChunk = this.chunker.nextChunk();
+    if (nextChunk !== null) {
+      this.currentChunkPosition += chunkLength;
+      this.offsetInChunk = 0;
+    } else {
+      this.offsetInChunk = chunkLength;
+    }
+    return [data, nextChunk];
+  }
+
+  // Read backwards to the end of the previous chunk, if any; otherwise to the start of the current chunk.
+  _readToPreviousChunk(): [string, TChunk | null] {
+    const data = this.currentChunk.data.substring(0, this.offsetInChunk);
+    const previousChunk = this.chunker.previousChunk();
+    if (previousChunk !== null) {
+      this.currentChunkPosition -= this.currentChunk.data.length;
+      this.offsetInChunk = this.currentChunk.data.length;
+    } else {
+      this.offsetInChunk = 0;
+    }
+    return [data, previousChunk];
+  }
+}
diff --git a/packages/selector/src/types.ts b/packages/selector/src/types.ts
index 2cf0929..fff5199 100644
--- a/packages/selector/src/types.ts
+++ b/packages/selector/src/types.ts
@@ -18,18 +18,51 @@
  * under the License.
  */
 
+/**
+ * A {@link https://www.w3.org/TR/2017/REC-annotation-model-20170223/#selectors
+ * | Selector} object of the Web Annotation Data Model.
+ *
+ * Corresponds to RDF class {@link http://www.w3.org/ns/oa#Selector}
+ *
+ * @public
+ */
 export interface Selector {
+  /**
+   * A Selector can be refined by another Selector.
+   *
+   * See {@link https://www.w3.org/TR/2017/REC-annotation-model-20170223/#refinement-of-selection
+   * | §4.2.9 Refinement of Selection} in the Web Annotation Data Model.
+   *
+   * Corresponds to RDF property {@link http://www.w3.org/ns/oa#refinedBy}
+   */
   refinedBy?: this;
+
   type?: SelectorType;
 }
 
 export type SelectorType = string; // not enumerating known options: we allow extensibility.
 
+/**
+ * The {@link https://www.w3.org/TR/2017/REC-annotation-model-20170223/#css-selector
+ * | CssSelector} of the Web Annotation Data Model.
+ *
+ * Corresponds to RDF class {@link http://www.w3.org/ns/oa#CssSelector}
+ *
+ * @public
+ */
 export interface CssSelector extends Selector {
   type: 'CssSelector';
   value: string;
 }
 
+/**
+ * The {@link https://www.w3.org/TR/2017/REC-annotation-model-20170223/#text-quote-selector
+ * | TextQuoteSelector} of the Web Annotation Data Model.
+ *
+ * Corresponds to RDF class {@link http://www.w3.org/ns/oa#TextQuoteSelector}
+ *
+ * @public
+ */
 export interface TextQuoteSelector extends Selector {
   type: 'TextQuoteSelector';
   exact: string;
@@ -37,12 +70,40 @@
   suffix?: string;
 }
 
+/**
+ * The {@link https://www.w3.org/TR/2017/REC-annotation-model-20170223/#text-position-selector
+ * | TextPositionSelector} of the Web Annotation Data Model.
+ *
+ * Corresponds to RDF class {@link http://www.w3.org/ns/oa#TextPositionSelector}
+ *
+ * @public
+ */
+export interface TextPositionSelector extends Selector {
+  type: 'TextPositionSelector';
+  start: number; // more precisely: non-negative integer
+  end: number; // more precisely: non-negative integer
+}
+
+/**
+ * The {@link https://www.w3.org/TR/2017/REC-annotation-model-20170223/#range-selector
+ * | RangeSelector} of the Web Annotation Data Model.
+ *
+ * Corresponds to RDF class {@link http://www.w3.org/ns/oa#RangeSelector}
+ *
+ * @public
+ */
 export interface RangeSelector extends Selector {
   type: 'RangeSelector';
   startSelector: Selector;
   endSelector: Selector;
 }
 
+/**
+ * A function that finds the match(es) in the given (sub)document (the ‘scope’)
+ * corresponding to some (prespecified) selector(s).
+ *
+ * @public
+ */
 export interface Matcher<TScope, TMatch> {
   (scope: TScope): AsyncGenerator<TMatch, void, void>;
 }
diff --git a/test/data-model.test.ts b/test/data-model.test.ts
index 2082d3a..2d1d9e6 100644
--- a/test/data-model.test.ts
+++ b/test/data-model.test.ts
@@ -22,7 +22,6 @@
 
 import fs from 'fs';
 import { URL } from 'url';
-
 import Ajv from 'ajv';
 import { assert } from 'chai';
 import fetch from 'node-fetch';
@@ -95,10 +94,10 @@
     }
   });
 
-  const assertions = MUSTS.assertions as [string];
+  const assertions = MUSTS['assertions'] as [string];
   assertions.forEach((schemaPath: string) => {
     const schema = requireJSON(`web-annotation-tests/${schemaPath}`);
-    it(schema.title as string, () => {
+    it(schema['title'] as string, () => {
       const valid = ajv.validate(schema, data);
       assert.isOk(valid, ajv.errorsText());
     });
diff --git a/tsconfig.base.json b/tsconfig.base.json
index 274039b..3070cfb 100644
--- a/tsconfig.base.json
+++ b/tsconfig.base.json
@@ -1,11 +1,12 @@
 {
   "compilerOptions": {
-    "allowSyntheticDefaultImports": true,
     "composite": true,
     "declaration": true,
     "declarationMap": true,
     "downlevelIteration": true,
     "emitDeclarationOnly": true,
+    "esModuleInterop": true,
+    "forceConsistentCasingInFileNames": true,
     "isolatedModules": true,
     "lib": [
       "dom",
@@ -13,7 +14,9 @@
       "es2020"
     ],
     "moduleResolution": "node",
+    "noPropertyAccessFromIndexSignature": true,
+    "skipLibCheck": true,
     "strict": true,
-    "target": "es2018"
+    "target": "es2017"
   }
 }
diff --git a/tsconfig.json b/tsconfig.json
index 7685922..b9a2c0d 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,14 +1,9 @@
 {
-  "extends": "./tsconfig.base.json",
-  "include": [
-    "test/**/*",
-    "packages/*/test/**/*",
-  ],
+  "files": [],
   "references": [
+    { "path": "packages/apache-annotator" },
     { "path": "packages/dom" },
-    { "path": "packages/selector" }
-  ],
-  "compilerOptions": {
-    "lib": ["dom", "dom.iterable", "es2018"]
-  }
+    { "path": "packages/selector" },
+    { "path": "tsconfig.test.json"}
+  ]
 }
diff --git a/tsconfig.test.json b/tsconfig.test.json
new file mode 100644
index 0000000..f439fba
--- /dev/null
+++ b/tsconfig.test.json
@@ -0,0 +1,8 @@
+{
+  "extends": "./tsconfig.base.json",
+  "include": ["test", "packages/*/test"],
+  "references": [
+    { "path": "packages/dom" },
+    { "path": "packages/selector" }
+  ]
+}
diff --git a/typedoc.json b/typedoc.json
new file mode 100644
index 0000000..76bc567
--- /dev/null
+++ b/typedoc.json
@@ -0,0 +1,5 @@
+{
+  "disableSources": true,
+  "entryPoints": ["packages/apache-annotator/src/"],
+  "readme": "none"
+}
diff --git a/web/demo/index.html b/web/demo/index.html
deleted file mode 100644
index ad2d0a2..0000000
--- a/web/demo/index.html
+++ /dev/null
@@ -1,99 +0,0 @@
-<!--
-Licensed to the Apache Software Foundation (ASF) under one
-or more contributor license agreements.  See the NOTICE file
-distributed with this work for additional information
-regarding copyright ownership.  The ASF licenses this file
-to you under the Apache License, Version 2.0 (the
-"License"); you may not use this file except in compliance
-with the License.  You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing,
-software distributed under the License is distributed on an
-"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-KIND, either express or implied.  See the License for the
-specific language governing permissions and limitations
-under the License.
--->
-<!doctype html>
-<html>
-<head>
-  <meta charset="utf-8">
-  <meta http-equiv="X-UA-Compatible" content="IE=edge">
-  <meta name="viewport" content="width=device-width, initial-scale=1">
-  <title>Apache Annotator (incubating) demo</title>
-  <link rel="stylesheet" href="../style.css"/>
-  <style>
-    a[data-run-example]:before {
-      content: '📌 ';
-    }
-
-    #source, #target {
-      padding: 1em;
-      border: 1px solid lightgrey;
-      background: white;
-    }
-
-    #info {
-      height: 15em;
-      overflow: scroll;
-      background: white;
-      color: #666666;
-      resize: vertical;
-    }
-
-    .columns {
-      background: aliceblue;
-    }
-  </style>
-  <script defer src="index.js"></script>
-</head>
-<body>
-  <header>
-    <h1>Apache Annotator <small>(incubating)</small></h1>
-  </header>
-  <nav>
-    <a href="../">← Back</a>
-  </nav>
-  <main>
-    <h1>Selector Demonstration</h1>
-
-    <p>This page demonstrates Web Annotation
-      <a href="https://www.w3.org/TR/2017/REC-annotation-model-20170223/#selectors" target="_blank">Selectors</a>,
-      standardised JSON objects that describe a selection inside a document with sufficient information to find it back.</p>
-      <p>This demo’s source code can be found <a href="https://gitbox.apache.org/repos/asf?p=incubator-annotator.git;a=tree;hb=HEAD;f=web/demo">in the project repo</a> (also mirrored <a href="https://github.com/apache/incubator-annotator/tree/master/web/demo">on GitHub</a>)</p>
-
-    <div class="columns full-width">
-      <div class="column">
-        <h2>Select text here</h2>
-        <p id="source">Hello, annotated world! To annotate, or not to annotate, that is the question.</p>
-        <p>Try selecting some text in this paragraph above.
-          Upon a change of selection, a
-          <a rel="external" href="https://www.w3.org/TR/2017/REC-annotation-model-20170223/#text-quote-selector" target="_blank">TextQuoteSelector</a>
-          will be created, that describes what was selected.</p>
-      </div>
-      <div class="column">
-        <h2>Text is found here</h2>
-        <p id="target" contenteditable>Hello, annotated world! To annotate, or not to annotate, that is the question.</p>
-        <p>The selector is ‘anchored’ here: the segment it describes is found and highlighted.</p>
-      </div>
-      <div class="column" style="min-width: 20em;">
-        <h2>The selector as JSON:</h2>
-        <pre id="info"></pre>
-      </div>
-    </div>
-    <p>Here are other selectors you can anchor in the text above:</p>
-    <ul>
-      <li><a href="#" data-run-example="0">An ambiguous selector (i.e. with multiple matches)</a>
-      <li>
-        <a href="#" data-run-example="1">RangeSelector</a>
-        (<a href="https://www.w3.org/TR/2017/REC-annotation-model-20170223/#range-selector" target="_blank">spec</a>)
-      <li>
-        <a href="#" data-run-example="2">Refining a selector using another selector</a>
-        (<a href="https://www.w3.org/TR/2017/REC-annotation-model-20170223/#refinement-of-selection" target="blank">spec</a>)
-      <li><a href="#" data-run-example="3">Any deeper nesting of the above</a>
-    </ul>
-  </main>
-</body>
-</html>
diff --git a/web/index.html b/web/index.html
index 85dae62..8108f5b 100644
--- a/web/index.html
+++ b/web/index.html
@@ -22,16 +22,83 @@
   <meta charset="utf-8">
   <meta http-equiv="X-UA-Compatible" content="IE=edge">
   <meta name="viewport" content="width=device-width, initial-scale=1">
-  <title>Apache Annotator (incubating)</title>
-  <link rel="stylesheet" href="style.css">
+  <title>Apache Annotator (incubating) demo</title>
+  <link rel="stylesheet" href="style.css"/>
+  <style>
+    a[data-run-example]:before {
+      content: '📌 ';
+    }
+
+    #source, #target {
+      padding: 1em;
+      border: 1px solid lightgrey;
+      background: white;
+    }
+
+    #info {
+      height: 15em;
+      overflow: scroll;
+      background: white;
+      color: #666666;
+      resize: vertical;
+    }
+
+    .columns {
+      background: aliceblue;
+    }
+  </style>
+  <script defer src="main.js"></script>
 </head>
 <body>
+  <header>
+    <h1><a href="https://annotator.apache.org">Apache Annotator</a> <small>(incubating)</small></h1>
+  </header>
   <main>
-    <h1>Welcome to Apache Annotator (incubating)</h1>
-    <h2>Getting Started</h2>
+    <h1>Selector Demonstration</h1>
+
+    <p>This page demonstrates Web Annotation
+      <a href="https://www.w3.org/TR/2017/REC-annotation-model-20170223/#selectors" target="_blank">Selectors</a>,
+      standardised JSON objects that describe a selection inside a document with sufficient information to find it back.</p>
+      <p>This demo’s source code can be found <a href="https://gitbox.apache.org/repos/asf?p=incubator-annotator.git;a=tree;hb=HEAD;f=web">in the project repo</a> (also mirrored <a href="https://github.com/apache/incubator-annotator/tree/master/web">on GitHub</a>)</p>
+
+    <div class="columns full-width">
+      <div class="column">
+        <h2>Select text here</h2>
+        <p id="source" contenteditable>Hello, <em>annotated world!</em> 🙂 <b>To annotate, or <em>not</em> to annotate</b>, that is the question.</p>
+        <p>Try selecting some text in this paragraph above.
+          Upon a change of selection, a
+          <a rel="external" href="https://www.w3.org/TR/2017/REC-annotation-model-20170223/#text-quote-selector" target="_blank">TextQuoteSelector</a>
+          will be created, that describes what was selected.</p>
+          <form id="form">
+            The selector can work either
+            <br/>
+            <input type="radio" name="describeMode" value="TextQuote" id="describeModeTextQuote" checked>
+            <label for="describeModeTextQuote">by quoting the selected text</label>; or
+            </br>
+            <input type="radio" name="describeMode" value="TextPosition" id="describeModeTextPosition">
+            <label for="describeModeTextPosition">by counting the selected characters’ position in the text</label>.
+          </form>
+      </div>
+      <div class="column">
+        <h2>Text is found here</h2>
+        <p id="target" contenteditable><em>Hello, annotated</em> world! 🙂 To annotate, or not to annotate, <b><em>that</em> is the question.</b></p>
+        <p>The selector is ‘anchored’ here: the segment it describes is found and highlighted.</p>
+      </div>
+      <div class="column" style="min-width: 20em;">
+        <h2>The selector as JSON:</h2>
+        <pre id="info"></pre>
+      </div>
+    </div>
+    <p>Here are other selectors you can anchor in the text above:</p>
     <ul>
-      <li>Visit the <a href="demo/index.html">demonstration page</a>.
-      <li>Run the <a href="test/index.html">test suite</a>.
+      <li><a href="#" data-run-example="0">An ambiguous selector (i.e. with multiple matches)</a>
+      <li>
+        <a href="#" data-run-example="1">RangeSelector</a>
+        (<a href="https://www.w3.org/TR/2017/REC-annotation-model-20170223/#range-selector" target="_blank">spec</a>)
+      <li>
+        <a href="#" data-run-example="2">Refining a selector using another selector</a>
+        (<a href="https://www.w3.org/TR/2017/REC-annotation-model-20170223/#refinement-of-selection" target="blank">spec</a>)
+      <li><a href="#" data-run-example="3">Any deeper nesting of the above</a>
     </ul>
   </main>
 </body>
diff --git a/web/demo/index.js b/web/index.js
similarity index 67%
rename from web/demo/index.js
rename to web/index.js
index fd5050e..d0c33f8 100644
--- a/web/demo/index.js
+++ b/web/index.js
@@ -18,23 +18,21 @@
  * under the License.
  */
 
-/* global info, module, source, target */
-// declare const module; // TODO type?
-// declare const info: HTMLElement;
-// declare const source: HTMLElement;
-// declare const target: HTMLElement;
+/* global info, module, source, target, form */
 
 import {
   createTextQuoteSelectorMatcher,
   describeTextQuote,
   supportRangeSelector,
+  createTextPositionSelectorMatcher,
+  describeTextPosition,
   highlightRange,
-} from '@annotator/dom';
+} from '@apache-annotator/dom';
 import {
   composeMatcherCreator,
   mapSelectorTypes,
   supportRefinement,
-} from '@annotator/selector';
+} from '@apache-annotator/selector';
 
 const EXAMPLE_SELECTORS = [
   {
@@ -89,14 +87,17 @@
   },
 ];
 
-const cleanupFunctions = [];
+let moduleState = {
+  cleanupFunctions: [],
+};
 
 function cleanup() {
   let removeHighlight;
-  while ((removeHighlight = cleanupFunctions.shift())) {
+  while ((removeHighlight = moduleState.cleanupFunctions.shift())) {
     removeHighlight();
   }
   target.normalize();
+  info.innerText = '';
 }
 
 const createMatcher = composeMatcherCreator(
@@ -104,6 +105,7 @@
   supportRangeSelector,
   mapSelectorTypes({
     TextQuoteSelector: createTextQuoteSelectorMatcher,
+    TextPositionSelector: createTextPositionSelectorMatcher,
   }),
 );
 
@@ -117,18 +119,24 @@
 
   for (const range of ranges) {
     const removeHighlight = highlightRange(range);
-    cleanupFunctions.push(removeHighlight);
+    moduleState.cleanupFunctions.push(removeHighlight);
   }
 
-  info.innerText = JSON.stringify(selector, null, 2);
+  info.innerText += JSON.stringify(selector, null, 2) + '\n\n';
 }
 
 async function onSelectionChange() {
   cleanup();
+  const describeMode = form.describeMode.value;
   const selection = document.getSelection();
-  const range = selection.getRangeAt(0);
-  const selector = await describeTextQuote(range, source);
-  anchor(selector);
+  for (let i = 0; i < selection.rangeCount; i++) {
+    const range = selection.getRangeAt(i);
+    const selector =
+      describeMode === 'TextPosition'
+        ? await describeTextPosition(range, source)
+        : await describeTextQuote(range, source, { minimumQuoteLength: 10 });
+    await anchor(selector);
+  }
 }
 
 function onSelectorExampleClick(event) {
@@ -140,13 +148,26 @@
   event.preventDefault();
 }
 
-document.addEventListener('selectionchange', onSelectionChange);
-document.addEventListener('click', onSelectorExampleClick);
+function addEventListeners() {
+  document.addEventListener('selectionchange', onSelectionChange);
+  form.addEventListener('change', onSelectionChange);
+  document.addEventListener('click', onSelectorExampleClick);
+}
+addEventListeners();
+
+function removeEventListeners() {
+  document.removeEventListener('selectionchange', onSelectionChange);
+  form.removeEventListener('change', onSelectionChange);
+  document.removeEventListener('click', onSelectorExampleClick);
+}
 
 if (module.hot) {
   module.hot.accept();
-  module.hot.dispose(() => {
-    document.removeEventListener('selectionchange', onSelectionChange);
-    document.removeEventListener('click', onSelectorExampleClick);
+  module.hot.dispose((data) => {
+    removeEventListeners();
+    data.state = moduleState;
   });
+  if (module.hot.data?.state) {
+    moduleState = module.hot.data.state;
+  }
 }
diff --git a/web/test/index.html b/web/test/index.html
deleted file mode 100644
index 2d00bef..0000000
--- a/web/test/index.html
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Licensed to the Apache Software Foundation (ASF) under one
-or more contributor license agreements.  See the NOTICE file
-distributed with this work for additional information
-regarding copyright ownership.  The ASF licenses this file
-to you under the Apache License, Version 2.0 (the
-"License"); you may not use this file except in compliance
-with the License.  You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing,
-software distributed under the License is distributed on an
-"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-KIND, either express or implied.  See the License for the
-specific language governing permissions and limitations
-under the License.
--->
-<!doctype html>
-<html>
-  <meta charset="utf-8">
-  <title>Apache Annotator (incubating) test suite</title>
-  <script src="index.js"></script>
-</html>
diff --git a/web/webpack.config.js b/web/webpack.config.js
index 5dcbfda..b5d2847 100644
--- a/web/webpack.config.js
+++ b/web/webpack.config.js
@@ -18,25 +18,11 @@
  * under the License.
  */
 
-/* eslint-env node */
-/* eslint-disable import/unambiguous */
-
 const path = require('path');
 
 module.exports = {
-  context: path.resolve(__dirname),
-  entry: {
-    index: ['./index.html', './style.css'],
-    demo: ['./demo/index.html', './demo/index.js'],
-    test: [
-      './test/index.html',
-      'mocha-loader!multi-entry-loader?include=./packages/*/test/**/*.test.[jt]s!',
-    ],
-  },
-  resolve: {
-    extensions: ['.ts', '.js'],
-  },
-  devtool: 'inline-source-map',
+  context: __dirname,
+  entry: ['./index.html', './index.js', './style.css'],
   module: {
     rules: [
       {
@@ -58,8 +44,8 @@
     ],
   },
   output: {
+    // Note this directory is imported by the annotator website
     path: path.resolve(__dirname, 'dist'),
-    filename: '[name]/index.js',
   },
   devServer: {
     contentBase: false,
diff --git a/yarn.lock b/yarn.lock
index 259c26b..50675a0 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2,793 +2,834 @@
 # yarn lockfile v1
 
 
-"@babel/cli@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/cli/-/cli-7.10.1.tgz#b6e5cd43a17b8f639442ab027976408ebe6d79a0"
-  integrity sha512-cVB+dXeGhMOqViIaZs3A9OUAe4pKw4SBNdMw6yHJMYR7s4TB+Cei7ThquV/84O19PdIFWuwe03vxxES0BHUm5g==
+"@babel/cli@^7.13.14":
+  version "7.13.14"
+  resolved "https://registry.yarnpkg.com/@babel/cli/-/cli-7.13.14.tgz#c395bc89ec4760c91f2027fa8b26f8b2bf42238f"
+  integrity sha512-zmEFV8WBRsW+mPQumO1/4b34QNALBVReaiHJOkxhUsdo/AvYM62c+SKSuLi2aZ42t3ocK6OI0uwUXRvrIbREZw==
   dependencies:
     commander "^4.0.1"
     convert-source-map "^1.1.0"
     fs-readdir-recursive "^1.1.0"
     glob "^7.0.0"
-    lodash "^4.17.13"
+    lodash "^4.17.19"
     make-dir "^2.1.0"
     slash "^2.0.0"
     source-map "^0.5.0"
   optionalDependencies:
-    chokidar "^2.1.8"
+    "@nicolo-ribaudo/chokidar-2" "2.1.8-no-fsevents"
+    chokidar "^3.4.0"
 
-"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.10.1.tgz#d5481c5095daa1c57e16e54c6f9198443afb49ff"
-  integrity sha512-IGhtTmpjGbYzcEDOw7DcQtbQSXcG9ftmAXtWTu9V936vDye4xjjekktFAtgZsWpzTj/X01jocB46mTywm/4SZw==
+"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13":
+  version "7.12.13"
+  resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.13.tgz#dcfc826beef65e75c50e21d3837d7d95798dd658"
+  integrity sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==
   dependencies:
-    "@babel/highlight" "^7.10.1"
+    "@babel/highlight" "^7.12.13"
 
-"@babel/compat-data@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.10.1.tgz#b1085ffe72cd17bf2c0ee790fc09f9626011b2db"
-  integrity sha512-CHvCj7So7iCkGKPRFUfryXIkU2gSBw7VSZFYLsqVhrS47269VK2Hfi9S/YcublPMW8k1u2bQBlbDruoQEm4fgw==
-  dependencies:
-    browserslist "^4.12.0"
-    invariant "^2.2.4"
-    semver "^5.5.0"
+"@babel/compat-data@^7.13.0", "@babel/compat-data@^7.13.12", "@babel/compat-data@^7.13.8":
+  version "7.13.12"
+  resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.13.12.tgz#a8a5ccac19c200f9dd49624cac6e19d7be1236a1"
+  integrity sha512-3eJJ841uKxeV8dcN/2yGEUy+RfgQspPEgQat85umsE1rotuquQ2AbIub4S6j7c50a2d+4myc+zSlnXeIHrOnhQ==
 
-"@babel/core@^7.10.1", "@babel/core@^7.7.5":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.10.1.tgz#2a0ad0ea693601820defebad2140206503d89af3"
-  integrity sha512-u8XiZ6sMXW/gPmoP5ijonSUln4unazG291X0XAQ5h0s8qnAFr6BRRZGUEK+jtRWdmB0NTJQt7Uga25q8GetIIg==
+"@babel/core@^7.13.14", "@babel/core@^7.7.5":
+  version "7.13.14"
+  resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.13.14.tgz#8e46ebbaca460a63497c797e574038ab04ae6d06"
+  integrity sha512-wZso/vyF4ki0l0znlgM4inxbdrUvCb+cVz8grxDq+6C9k6qbqoIJteQOKicaKjCipU3ISV+XedCqpL2RJJVehA==
   dependencies:
-    "@babel/code-frame" "^7.10.1"
-    "@babel/generator" "^7.10.1"
-    "@babel/helper-module-transforms" "^7.10.1"
-    "@babel/helpers" "^7.10.1"
-    "@babel/parser" "^7.10.1"
-    "@babel/template" "^7.10.1"
-    "@babel/traverse" "^7.10.1"
-    "@babel/types" "^7.10.1"
+    "@babel/code-frame" "^7.12.13"
+    "@babel/generator" "^7.13.9"
+    "@babel/helper-compilation-targets" "^7.13.13"
+    "@babel/helper-module-transforms" "^7.13.14"
+    "@babel/helpers" "^7.13.10"
+    "@babel/parser" "^7.13.13"
+    "@babel/template" "^7.12.13"
+    "@babel/traverse" "^7.13.13"
+    "@babel/types" "^7.13.14"
     convert-source-map "^1.7.0"
     debug "^4.1.0"
-    gensync "^1.0.0-beta.1"
+    gensync "^1.0.0-beta.2"
     json5 "^2.1.2"
-    lodash "^4.17.13"
-    resolve "^1.3.2"
-    semver "^5.4.1"
+    semver "^6.3.0"
     source-map "^0.5.0"
 
-"@babel/generator@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.10.1.tgz#4d14458e539bcb04ffe34124143f5c489f2dbca9"
-  integrity sha512-AT0YPLQw9DI21tliuJIdplVfLHya6mcGa8ctkv7n4Qv+hYacJrKmNWIteAK1P9iyLikFIAkwqJ7HAOqIDLFfgA==
+"@babel/generator@^7.13.9":
+  version "7.13.9"
+  resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.13.9.tgz#3a7aa96f9efb8e2be42d38d80e2ceb4c64d8de39"
+  integrity sha512-mHOOmY0Axl/JCTkxTU6Lf5sWOg/v8nUa+Xkt4zMTftX0wqmb6Sh7J8gvcehBw7q0AhrhAR+FDacKjCZ2X8K+Sw==
   dependencies:
-    "@babel/types" "^7.10.1"
+    "@babel/types" "^7.13.0"
     jsesc "^2.5.1"
-    lodash "^4.17.13"
     source-map "^0.5.0"
 
-"@babel/helper-annotate-as-pure@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.10.1.tgz#f6d08acc6f70bbd59b436262553fb2e259a1a268"
-  integrity sha512-ewp3rvJEwLaHgyWGe4wQssC2vjks3E80WiUe2BpMb0KhreTjMROCbxXcEovTrbeGVdQct5VjQfrv9EgC+xMzCw==
+"@babel/helper-annotate-as-pure@^7.12.13":
+  version "7.12.13"
+  resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.12.13.tgz#0f58e86dfc4bb3b1fcd7db806570e177d439b6ab"
+  integrity sha512-7YXfX5wQ5aYM/BOlbSccHDbuXXFPxeoUmfWtz8le2yTkTZc+BxsiEnENFoi2SlmA8ewDkG2LgIMIVzzn2h8kfw==
   dependencies:
-    "@babel/types" "^7.10.1"
+    "@babel/types" "^7.12.13"
 
-"@babel/helper-builder-binary-assignment-operator-visitor@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.10.1.tgz#0ec7d9be8174934532661f87783eb18d72290059"
-  integrity sha512-cQpVq48EkYxUU0xozpGCLla3wlkdRRqLWu1ksFMXA9CM5KQmyyRpSEsYXbao7JUkOw/tAaYKCaYyZq6HOFYtyw==
+"@babel/helper-builder-binary-assignment-operator-visitor@^7.12.13":
+  version "7.12.13"
+  resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.12.13.tgz#6bc20361c88b0a74d05137a65cac8d3cbf6f61fc"
+  integrity sha512-CZOv9tGphhDRlVjVkAgm8Nhklm9RzSmWpX2my+t7Ua/KT616pEzXsQCjinzvkRvHWJ9itO4f296efroX23XCMA==
   dependencies:
-    "@babel/helper-explode-assignable-expression" "^7.10.1"
-    "@babel/types" "^7.10.1"
+    "@babel/helper-explode-assignable-expression" "^7.12.13"
+    "@babel/types" "^7.12.13"
 
-"@babel/helper-compilation-targets@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.10.1.tgz#ad6f69b4c3bae955081ef914a84e5878ffcaca63"
-  integrity sha512-YuF8IrgSmX/+MV2plPkjEnzlC2wf+gaok8ehMNN0jodF3/sejZauExqpEVGbJua62oaWoNYIXwz4RmAsVcGyHw==
+"@babel/helper-compilation-targets@^7.13.0", "@babel/helper-compilation-targets@^7.13.10", "@babel/helper-compilation-targets@^7.13.13", "@babel/helper-compilation-targets@^7.13.8":
+  version "7.13.13"
+  resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.13.13.tgz#2b2972a0926474853f41e4adbc69338f520600e5"
+  integrity sha512-q1kcdHNZehBwD9jYPh3WyXcsFERi39X4I59I3NadciWtNDyZ6x+GboOxncFK0kXlKIv6BJm5acncehXWUjWQMQ==
   dependencies:
-    "@babel/compat-data" "^7.10.1"
-    browserslist "^4.12.0"
-    invariant "^2.2.4"
-    levenary "^1.1.1"
-    semver "^5.5.0"
+    "@babel/compat-data" "^7.13.12"
+    "@babel/helper-validator-option" "^7.12.17"
+    browserslist "^4.14.5"
+    semver "^6.3.0"
 
-"@babel/helper-create-class-features-plugin@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.10.1.tgz#6d8a45aafe492378d0e6fc0b33e5dea132eae21c"
-  integrity sha512-bwhdehBJZt84HuPUcP1HaTLuc/EywVS8rc3FgsEPDcivg+DCW+SHuLHVkYOmcBA1ZfI+Z/oZjQc/+bPmIO7uAA==
+"@babel/helper-create-class-features-plugin@^7.13.0":
+  version "7.13.11"
+  resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.13.11.tgz#30d30a005bca2c953f5653fc25091a492177f4f6"
+  integrity sha512-ays0I7XYq9xbjCSvT+EvysLgfc3tOkwCULHjrnscGT3A9qD4sk3wXnJ3of0MAWsWGjdinFvajHU2smYuqXKMrw==
   dependencies:
-    "@babel/helper-function-name" "^7.10.1"
-    "@babel/helper-member-expression-to-functions" "^7.10.1"
-    "@babel/helper-optimise-call-expression" "^7.10.1"
-    "@babel/helper-plugin-utils" "^7.10.1"
-    "@babel/helper-replace-supers" "^7.10.1"
-    "@babel/helper-split-export-declaration" "^7.10.1"
+    "@babel/helper-function-name" "^7.12.13"
+    "@babel/helper-member-expression-to-functions" "^7.13.0"
+    "@babel/helper-optimise-call-expression" "^7.12.13"
+    "@babel/helper-replace-supers" "^7.13.0"
+    "@babel/helper-split-export-declaration" "^7.12.13"
 
-"@babel/helper-create-regexp-features-plugin@^7.10.1", "@babel/helper-create-regexp-features-plugin@^7.8.3":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.10.1.tgz#1b8feeab1594cbcfbf3ab5a3bbcabac0468efdbd"
-  integrity sha512-Rx4rHS0pVuJn5pJOqaqcZR4XSgeF9G/pO/79t+4r7380tXFJdzImFnxMU19f83wjSrmKHq6myrM10pFHTGzkUA==
+"@babel/helper-create-regexp-features-plugin@^7.12.13":
+  version "7.12.17"
+  resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.12.17.tgz#a2ac87e9e319269ac655b8d4415e94d38d663cb7"
+  integrity sha512-p2VGmBu9oefLZ2nQpgnEnG0ZlRPvL8gAGvPUMQwUdaE8k49rOMuZpOwdQoy5qJf6K8jL3bcAMhVUlHAjIgJHUg==
   dependencies:
-    "@babel/helper-annotate-as-pure" "^7.10.1"
-    "@babel/helper-regex" "^7.10.1"
-    regexpu-core "^4.7.0"
+    "@babel/helper-annotate-as-pure" "^7.12.13"
+    regexpu-core "^4.7.1"
 
-"@babel/helper-define-map@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/helper-define-map/-/helper-define-map-7.10.1.tgz#5e69ee8308648470dd7900d159c044c10285221d"
-  integrity sha512-+5odWpX+OnvkD0Zmq7panrMuAGQBu6aPUgvMzuMGo4R+jUOvealEj2hiqI6WhxgKrTpFoFj0+VdsuA8KDxHBDg==
+"@babel/helper-define-polyfill-provider@^0.1.5":
+  version "0.1.5"
+  resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.1.5.tgz#3c2f91b7971b9fc11fe779c945c014065dea340e"
+  integrity sha512-nXuzCSwlJ/WKr8qxzW816gwyT6VZgiJG17zR40fou70yfAcqjoNyTLl/DQ+FExw5Hx5KNqshmN8Ldl/r2N7cTg==
   dependencies:
-    "@babel/helper-function-name" "^7.10.1"
-    "@babel/types" "^7.10.1"
-    lodash "^4.17.13"
+    "@babel/helper-compilation-targets" "^7.13.0"
+    "@babel/helper-module-imports" "^7.12.13"
+    "@babel/helper-plugin-utils" "^7.13.0"
+    "@babel/traverse" "^7.13.0"
+    debug "^4.1.1"
+    lodash.debounce "^4.0.8"
+    resolve "^1.14.2"
+    semver "^6.1.2"
 
-"@babel/helper-explode-assignable-expression@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.10.1.tgz#e9d76305ee1162ca467357ae25df94f179af2b7e"
-  integrity sha512-vcUJ3cDjLjvkKzt6rHrl767FeE7pMEYfPanq5L16GRtrXIoznc0HykNW2aEYkcnP76P0isoqJ34dDMFZwzEpJg==
+"@babel/helper-explode-assignable-expression@^7.12.13":
+  version "7.13.0"
+  resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.13.0.tgz#17b5c59ff473d9f956f40ef570cf3a76ca12657f"
+  integrity sha512-qS0peLTDP8kOisG1blKbaoBg/o9OSa1qoumMjTK5pM+KDTtpxpsiubnCGP34vK8BXGcb2M9eigwgvoJryrzwWA==
   dependencies:
-    "@babel/traverse" "^7.10.1"
-    "@babel/types" "^7.10.1"
+    "@babel/types" "^7.13.0"
 
-"@babel/helper-function-name@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.10.1.tgz#92bd63829bfc9215aca9d9defa85f56b539454f4"
-  integrity sha512-fcpumwhs3YyZ/ttd5Rz0xn0TpIwVkN7X0V38B9TWNfVF42KEkhkAAuPCQ3oXmtTRtiPJrmZ0TrfS0GKF0eMaRQ==
+"@babel/helper-function-name@^7.12.13":
+  version "7.12.13"
+  resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.12.13.tgz#93ad656db3c3c2232559fd7b2c3dbdcbe0eb377a"
+  integrity sha512-TZvmPn0UOqmvi5G4vvw0qZTpVptGkB1GL61R6lKvrSdIxGm5Pky7Q3fpKiIkQCAtRCBUwB0PaThlx9vebCDSwA==
   dependencies:
-    "@babel/helper-get-function-arity" "^7.10.1"
-    "@babel/template" "^7.10.1"
-    "@babel/types" "^7.10.1"
+    "@babel/helper-get-function-arity" "^7.12.13"
+    "@babel/template" "^7.12.13"
+    "@babel/types" "^7.12.13"
 
-"@babel/helper-get-function-arity@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.1.tgz#7303390a81ba7cb59613895a192b93850e373f7d"
-  integrity sha512-F5qdXkYGOQUb0hpRaPoetF9AnsXknKjWMZ+wmsIRsp5ge5sFh4c3h1eH2pRTTuy9KKAA2+TTYomGXAtEL2fQEw==
+"@babel/helper-get-function-arity@^7.12.13":
+  version "7.12.13"
+  resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.13.tgz#bc63451d403a3b3082b97e1d8b3fe5bd4091e583"
+  integrity sha512-DjEVzQNz5LICkzN0REdpD5prGoidvbdYk1BVgRUOINaWJP2t6avB27X1guXK1kXNrX0WMfsrm1A/ZBthYuIMQg==
   dependencies:
-    "@babel/types" "^7.10.1"
+    "@babel/types" "^7.12.13"
 
-"@babel/helper-hoist-variables@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.10.1.tgz#7e77c82e5dcae1ebf123174c385aaadbf787d077"
-  integrity sha512-vLm5srkU8rI6X3+aQ1rQJyfjvCBLXP8cAGeuw04zeAM2ItKb1e7pmVmLyHb4sDaAYnLL13RHOZPLEtcGZ5xvjg==
+"@babel/helper-hoist-variables@^7.13.0":
+  version "7.13.0"
+  resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.13.0.tgz#5d5882e855b5c5eda91e0cadc26c6e7a2c8593d8"
+  integrity sha512-0kBzvXiIKfsCA0y6cFEIJf4OdzfpRuNk4+YTeHZpGGc666SATFKTz6sRncwFnQk7/ugJ4dSrCj6iJuvW4Qwr2g==
   dependencies:
-    "@babel/types" "^7.10.1"
+    "@babel/traverse" "^7.13.0"
+    "@babel/types" "^7.13.0"
 
-"@babel/helper-member-expression-to-functions@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.10.1.tgz#432967fd7e12a4afef66c4687d4ca22bc0456f15"
-  integrity sha512-u7XLXeM2n50gb6PWJ9hoO5oO7JFPaZtrh35t8RqKLT1jFKj9IWeD1zrcrYp1q1qiZTdEarfDWfTIP8nGsu0h5g==
+"@babel/helper-member-expression-to-functions@^7.13.0", "@babel/helper-member-expression-to-functions@^7.13.12":
+  version "7.13.12"
+  resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.13.12.tgz#dfe368f26d426a07299d8d6513821768216e6d72"
+  integrity sha512-48ql1CLL59aKbU94Y88Xgb2VFy7a95ykGRbJJaaVv+LX5U8wFpLfiGXJJGUozsmA1oEh/o5Bp60Voq7ACyA/Sw==
   dependencies:
-    "@babel/types" "^7.10.1"
+    "@babel/types" "^7.13.12"
 
-"@babel/helper-module-imports@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.10.1.tgz#dd331bd45bccc566ce77004e9d05fe17add13876"
-  integrity sha512-SFxgwYmZ3HZPyZwJRiVNLRHWuW2OgE5k2nrVs6D9Iv4PPnXVffuEHy83Sfx/l4SqF+5kyJXjAyUmrG7tNm+qVg==
+"@babel/helper-module-imports@^7.12.13", "@babel/helper-module-imports@^7.13.12":
+  version "7.13.12"
+  resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.13.12.tgz#c6a369a6f3621cb25da014078684da9196b61977"
+  integrity sha512-4cVvR2/1B693IuOvSI20xqqa/+bl7lqAMR59R4iu39R9aOX8/JoYY1sFaNvUMyMBGnHdwvJgUrzNLoUZxXypxA==
   dependencies:
-    "@babel/types" "^7.10.1"
+    "@babel/types" "^7.13.12"
 
-"@babel/helper-module-transforms@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.10.1.tgz#24e2f08ee6832c60b157bb0936c86bef7210c622"
-  integrity sha512-RLHRCAzyJe7Q7sF4oy2cB+kRnU4wDZY/H2xJFGof+M+SJEGhZsb+GFj5j1AD8NiSaVBJ+Pf0/WObiXu/zxWpFg==
+"@babel/helper-module-transforms@^7.13.0", "@babel/helper-module-transforms@^7.13.14":
+  version "7.13.14"
+  resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.13.14.tgz#e600652ba48ccb1641775413cb32cfa4e8b495ef"
+  integrity sha512-QuU/OJ0iAOSIatyVZmfqB0lbkVP0kDRiKj34xy+QNsnVZi/PA6BoSoreeqnxxa9EHFAIL0R9XOaAR/G9WlIy5g==
   dependencies:
-    "@babel/helper-module-imports" "^7.10.1"
-    "@babel/helper-replace-supers" "^7.10.1"
-    "@babel/helper-simple-access" "^7.10.1"
-    "@babel/helper-split-export-declaration" "^7.10.1"
-    "@babel/template" "^7.10.1"
-    "@babel/types" "^7.10.1"
-    lodash "^4.17.13"
+    "@babel/helper-module-imports" "^7.13.12"
+    "@babel/helper-replace-supers" "^7.13.12"
+    "@babel/helper-simple-access" "^7.13.12"
+    "@babel/helper-split-export-declaration" "^7.12.13"
+    "@babel/helper-validator-identifier" "^7.12.11"
+    "@babel/template" "^7.12.13"
+    "@babel/traverse" "^7.13.13"
+    "@babel/types" "^7.13.14"
 
-"@babel/helper-optimise-call-expression@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.1.tgz#b4a1f2561870ce1247ceddb02a3860fa96d72543"
-  integrity sha512-a0DjNS1prnBsoKx83dP2falChcs7p3i8VMzdrSbfLhuQra/2ENC4sbri34dz/rWmDADsmF1q5GbfaXydh0Jbjg==
+"@babel/helper-optimise-call-expression@^7.12.13":
+  version "7.12.13"
+  resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.12.13.tgz#5c02d171b4c8615b1e7163f888c1c81c30a2aaea"
+  integrity sha512-BdWQhoVJkp6nVjB7nkFWcn43dkprYauqtk++Py2eaf/GRDFm5BxRqEIZCiHlZUGAVmtwKcsVL1dC68WmzeFmiA==
   dependencies:
-    "@babel/types" "^7.10.1"
+    "@babel/types" "^7.12.13"
 
-"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.1", "@babel/helper-plugin-utils@^7.8.0":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.1.tgz#ec5a5cf0eec925b66c60580328b122c01230a127"
-  integrity sha512-fvoGeXt0bJc7VMWZGCAEBEMo/HAjW2mP8apF5eXK0wSqwLAVHAISCWRoLMBMUs2kqeaG77jltVqu4Hn8Egl3nA==
+"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.13.0", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3":
+  version "7.13.0"
+  resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.13.0.tgz#806526ce125aed03373bc416a828321e3a6a33af"
+  integrity sha512-ZPafIPSwzUlAoWT8DKs1W2VyF2gOWthGd5NGFMsBcMMol+ZhK+EQY/e6V96poa6PA/Bh+C9plWN0hXO1uB8AfQ==
 
-"@babel/helper-plugin-utils@^7.10.4":
-  version "7.10.4"
-  resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz#2f75a831269d4f677de49986dff59927533cf375"
-  integrity sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==
-
-"@babel/helper-regex@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/helper-regex/-/helper-regex-7.10.1.tgz#021cf1a7ba99822f993222a001cc3fec83255b96"
-  integrity sha512-7isHr19RsIJWWLLFn21ubFt223PjQyg1HY7CZEMRr820HttHPpVvrsIN3bUOo44DEfFV4kBXO7Abbn9KTUZV7g==
+"@babel/helper-remap-async-to-generator@^7.13.0":
+  version "7.13.0"
+  resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.13.0.tgz#376a760d9f7b4b2077a9dd05aa9c3927cadb2209"
+  integrity sha512-pUQpFBE9JvC9lrQbpX0TmeNIy5s7GnZjna2lhhcHC7DzgBs6fWn722Y5cfwgrtrqc7NAJwMvOa0mKhq6XaE4jg==
   dependencies:
-    lodash "^4.17.13"
+    "@babel/helper-annotate-as-pure" "^7.12.13"
+    "@babel/helper-wrap-function" "^7.13.0"
+    "@babel/types" "^7.13.0"
 
-"@babel/helper-remap-async-to-generator@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.10.1.tgz#bad6aaa4ff39ce8d4b82ccaae0bfe0f7dbb5f432"
-  integrity sha512-RfX1P8HqsfgmJ6CwaXGKMAqbYdlleqglvVtht0HGPMSsy2V6MqLlOJVF/0Qyb/m2ZCi2z3q3+s6Pv7R/dQuZ6A==
+"@babel/helper-replace-supers@^7.12.13", "@babel/helper-replace-supers@^7.13.0", "@babel/helper-replace-supers@^7.13.12":
+  version "7.13.12"
+  resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.13.12.tgz#6442f4c1ad912502481a564a7386de0c77ff3804"
+  integrity sha512-Gz1eiX+4yDO8mT+heB94aLVNCL+rbuT2xy4YfyNqu8F+OI6vMvJK891qGBTqL9Uc8wxEvRW92Id6G7sDen3fFw==
   dependencies:
-    "@babel/helper-annotate-as-pure" "^7.10.1"
-    "@babel/helper-wrap-function" "^7.10.1"
-    "@babel/template" "^7.10.1"
-    "@babel/traverse" "^7.10.1"
-    "@babel/types" "^7.10.1"
+    "@babel/helper-member-expression-to-functions" "^7.13.12"
+    "@babel/helper-optimise-call-expression" "^7.12.13"
+    "@babel/traverse" "^7.13.0"
+    "@babel/types" "^7.13.12"
 
-"@babel/helper-replace-supers@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.10.1.tgz#ec6859d20c5d8087f6a2dc4e014db7228975f13d"
-  integrity sha512-SOwJzEfpuQwInzzQJGjGaiG578UYmyi2Xw668klPWV5n07B73S0a9btjLk/52Mlcxa+5AdIYqws1KyXRfMoB7A==
+"@babel/helper-simple-access@^7.12.13", "@babel/helper-simple-access@^7.13.12":
+  version "7.13.12"
+  resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.13.12.tgz#dd6c538afb61819d205a012c31792a39c7a5eaf6"
+  integrity sha512-7FEjbrx5SL9cWvXioDbnlYTppcZGuCY6ow3/D5vMggb2Ywgu4dMrpTJX0JdQAIcRRUElOIxF3yEooa9gUb9ZbA==
   dependencies:
-    "@babel/helper-member-expression-to-functions" "^7.10.1"
-    "@babel/helper-optimise-call-expression" "^7.10.1"
-    "@babel/traverse" "^7.10.1"
-    "@babel/types" "^7.10.1"
+    "@babel/types" "^7.13.12"
 
-"@babel/helper-simple-access@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.10.1.tgz#08fb7e22ace9eb8326f7e3920a1c2052f13d851e"
-  integrity sha512-VSWpWzRzn9VtgMJBIWTZ+GP107kZdQ4YplJlCmIrjoLVSi/0upixezHCDG8kpPVTBJpKfxTH01wDhh+jS2zKbw==
+"@babel/helper-skip-transparent-expression-wrappers@^7.12.1":
+  version "7.12.1"
+  resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.12.1.tgz#462dc63a7e435ade8468385c63d2b84cce4b3cbf"
+  integrity sha512-Mf5AUuhG1/OCChOJ/HcADmvcHM42WJockombn8ATJG3OnyiSxBK/Mm5x78BQWvmtXZKHgbjdGL2kin/HOLlZGA==
   dependencies:
-    "@babel/template" "^7.10.1"
-    "@babel/types" "^7.10.1"
+    "@babel/types" "^7.12.1"
 
-"@babel/helper-split-export-declaration@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.10.1.tgz#c6f4be1cbc15e3a868e4c64a17d5d31d754da35f"
-  integrity sha512-UQ1LVBPrYdbchNhLwj6fetj46BcFwfS4NllJo/1aJsT+1dLTEnXJL0qHqtY7gPzF8S2fXBJamf1biAXV3X077g==
+"@babel/helper-split-export-declaration@^7.12.13":
+  version "7.12.13"
+  resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.13.tgz#e9430be00baf3e88b0e13e6f9d4eaf2136372b05"
+  integrity sha512-tCJDltF83htUtXx5NLcaDqRmknv652ZWCHyoTETf1CXYJdPC7nohZohjUgieXhv0hTJdRf2FjDueFehdNucpzg==
   dependencies:
-    "@babel/types" "^7.10.1"
+    "@babel/types" "^7.12.13"
 
-"@babel/helper-validator-identifier@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.1.tgz#5770b0c1a826c4f53f5ede5e153163e0318e94b5"
-  integrity sha512-5vW/JXLALhczRCWP0PnFDMCJAchlBvM7f4uk/jXritBnIa6E1KmqmtrS3yn1LAnxFBypQ3eneLuXjsnfQsgILw==
+"@babel/helper-validator-identifier@^7.12.11":
+  version "7.12.11"
+  resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz#c9a1f021917dcb5ccf0d4e453e399022981fc9ed"
+  integrity sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==
 
-"@babel/helper-wrap-function@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.10.1.tgz#956d1310d6696257a7afd47e4c42dfda5dfcedc9"
-  integrity sha512-C0MzRGteVDn+H32/ZgbAv5r56f2o1fZSA/rj/TYo8JEJNHg+9BdSmKBUND0shxWRztWhjlT2cvHYuynpPsVJwQ==
+"@babel/helper-validator-option@^7.12.17":
+  version "7.12.17"
+  resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.12.17.tgz#d1fbf012e1a79b7eebbfdc6d270baaf8d9eb9831"
+  integrity sha512-TopkMDmLzq8ngChwRlyjR6raKD6gMSae4JdYDB8bByKreQgG0RBTuKe9LRxW3wFtUnjxOPRKBDwEH6Mg5KeDfw==
+
+"@babel/helper-wrap-function@^7.13.0":
+  version "7.13.0"
+  resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.13.0.tgz#bdb5c66fda8526ec235ab894ad53a1235c79fcc4"
+  integrity sha512-1UX9F7K3BS42fI6qd2A4BjKzgGjToscyZTdp1DjknHLCIvpgne6918io+aL5LXFcER/8QWiwpoY902pVEqgTXA==
   dependencies:
-    "@babel/helper-function-name" "^7.10.1"
-    "@babel/template" "^7.10.1"
-    "@babel/traverse" "^7.10.1"
-    "@babel/types" "^7.10.1"
+    "@babel/helper-function-name" "^7.12.13"
+    "@babel/template" "^7.12.13"
+    "@babel/traverse" "^7.13.0"
+    "@babel/types" "^7.13.0"
 
-"@babel/helpers@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.10.1.tgz#a6827b7cb975c9d9cef5fd61d919f60d8844a973"
-  integrity sha512-muQNHF+IdU6wGgkaJyhhEmI54MOZBKsFfsXFhboz1ybwJ1Kl7IHlbm2a++4jwrmY5UYsgitt5lfqo1wMFcHmyw==
+"@babel/helpers@^7.13.10":
+  version "7.13.10"
+  resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.13.10.tgz#fd8e2ba7488533cdeac45cc158e9ebca5e3c7df8"
+  integrity sha512-4VO883+MWPDUVRF3PhiLBUFHoX/bsLTGFpFK/HqvvfBZz2D57u9XzPVNFVBTc0PW/CWR9BXTOKt8NF4DInUHcQ==
   dependencies:
-    "@babel/template" "^7.10.1"
-    "@babel/traverse" "^7.10.1"
-    "@babel/types" "^7.10.1"
+    "@babel/template" "^7.12.13"
+    "@babel/traverse" "^7.13.0"
+    "@babel/types" "^7.13.0"
 
-"@babel/highlight@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.10.1.tgz#841d098ba613ba1a427a2b383d79e35552c38ae0"
-  integrity sha512-8rMof+gVP8mxYZApLF/JgNDAkdKa+aJt3ZYxF8z6+j/hpeXL7iMsKCPHa2jNMHu/qqBwzQF4OHNoYi8dMA/rYg==
+"@babel/highlight@^7.12.13":
+  version "7.13.10"
+  resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.13.10.tgz#a8b2a66148f5b27d666b15d81774347a731d52d1"
+  integrity sha512-5aPpe5XQPzflQrFwL1/QoeHkP2MsA4JCntcXHRhEsdsfPVkvPi2w7Qix4iV7t5S/oC9OodGrggd8aco1g3SZFg==
   dependencies:
-    "@babel/helper-validator-identifier" "^7.10.1"
+    "@babel/helper-validator-identifier" "^7.12.11"
     chalk "^2.0.0"
     js-tokens "^4.0.0"
 
-"@babel/parser@^7.10.1", "@babel/parser@^7.7.5":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.10.1.tgz#2e142c27ca58aa2c7b119d09269b702c8bbad28c"
-  integrity sha512-AUTksaz3FqugBkbTZ1i+lDLG5qy8hIzCaAxEtttU6C0BtZZU9pkNZtWSVAht4EW9kl46YBiyTGMp9xTTGqViNg==
+"@babel/parser@^7.12.13", "@babel/parser@^7.13.13", "@babel/parser@^7.7.5":
+  version "7.13.13"
+  resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.13.13.tgz#42f03862f4aed50461e543270916b47dd501f0df"
+  integrity sha512-OhsyMrqygfk5v8HmWwOzlYjJrtLaFhF34MrfG/Z73DgYCI6ojNUTUp2TYbtnjo8PegeJp12eamsNettCQjKjVw==
 
-"@babel/plugin-proposal-async-generator-functions@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.10.1.tgz#6911af5ba2e615c4ff3c497fe2f47b35bf6d7e55"
-  integrity sha512-vzZE12ZTdB336POZjmpblWfNNRpMSua45EYnRigE2XsZxcXcIyly2ixnTJasJE4Zq3U7t2d8rRF7XRUuzHxbOw==
+"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.13.12":
+  version "7.13.12"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.13.12.tgz#a3484d84d0b549f3fc916b99ee4783f26fabad2a"
+  integrity sha512-d0u3zWKcoZf379fOeJdr1a5WPDny4aOFZ6hlfKivgK0LY7ZxNfoaHL2fWwdGtHyVvra38FC+HVYkO+byfSA8AQ==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.10.1"
-    "@babel/helper-remap-async-to-generator" "^7.10.1"
-    "@babel/plugin-syntax-async-generators" "^7.8.0"
+    "@babel/helper-plugin-utils" "^7.13.0"
+    "@babel/helper-skip-transparent-expression-wrappers" "^7.12.1"
+    "@babel/plugin-proposal-optional-chaining" "^7.13.12"
 
-"@babel/plugin-proposal-class-properties@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.10.1.tgz#046bc7f6550bb08d9bd1d4f060f5f5a4f1087e01"
-  integrity sha512-sqdGWgoXlnOdgMXU+9MbhzwFRgxVLeiGBqTrnuS7LC2IBU31wSsESbTUreT2O418obpfPdGUR2GbEufZF1bpqw==
+"@babel/plugin-proposal-async-generator-functions@^7.13.8":
+  version "7.13.8"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.13.8.tgz#87aacb574b3bc4b5603f6fe41458d72a5a2ec4b1"
+  integrity sha512-rPBnhj+WgoSmgq+4gQUtXx/vOcU+UYtjy1AA/aeD61Hwj410fwYyqfUcRP3lR8ucgliVJL/G7sXcNUecC75IXA==
   dependencies:
-    "@babel/helper-create-class-features-plugin" "^7.10.1"
-    "@babel/helper-plugin-utils" "^7.10.1"
+    "@babel/helper-plugin-utils" "^7.13.0"
+    "@babel/helper-remap-async-to-generator" "^7.13.0"
+    "@babel/plugin-syntax-async-generators" "^7.8.4"
 
-"@babel/plugin-proposal-dynamic-import@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.10.1.tgz#e36979dc1dc3b73f6d6816fc4951da2363488ef0"
-  integrity sha512-Cpc2yUVHTEGPlmiQzXj026kqwjEQAD9I4ZC16uzdbgWgitg/UHKHLffKNCQZ5+y8jpIZPJcKcwsr2HwPh+w3XA==
+"@babel/plugin-proposal-class-properties@^7.13.0":
+  version "7.13.0"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.13.0.tgz#146376000b94efd001e57a40a88a525afaab9f37"
+  integrity sha512-KnTDjFNC1g+45ka0myZNvSBFLhNCLN+GeGYLDEA8Oq7MZ6yMgfLoIRh86GRT0FjtJhZw8JyUskP9uvj5pHM9Zg==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.10.1"
-    "@babel/plugin-syntax-dynamic-import" "^7.8.0"
+    "@babel/helper-create-class-features-plugin" "^7.13.0"
+    "@babel/helper-plugin-utils" "^7.13.0"
 
-"@babel/plugin-proposal-json-strings@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.10.1.tgz#b1e691ee24c651b5a5e32213222b2379734aff09"
-  integrity sha512-m8r5BmV+ZLpWPtMY2mOKN7wre6HIO4gfIiV+eOmsnZABNenrt/kzYBwrh+KOfgumSWpnlGs5F70J8afYMSJMBg==
+"@babel/plugin-proposal-dynamic-import@^7.13.8":
+  version "7.13.8"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.13.8.tgz#876a1f6966e1dec332e8c9451afda3bebcdf2e1d"
+  integrity sha512-ONWKj0H6+wIRCkZi9zSbZtE/r73uOhMVHh256ys0UzfM7I3d4n+spZNWjOnJv2gzopumP2Wxi186vI8N0Y2JyQ==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.10.1"
-    "@babel/plugin-syntax-json-strings" "^7.8.0"
+    "@babel/helper-plugin-utils" "^7.13.0"
+    "@babel/plugin-syntax-dynamic-import" "^7.8.3"
 
-"@babel/plugin-proposal-nullish-coalescing-operator@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.10.1.tgz#02dca21673842ff2fe763ac253777f235e9bbf78"
-  integrity sha512-56cI/uHYgL2C8HVuHOuvVowihhX0sxb3nnfVRzUeVHTWmRHTZrKuAh/OBIMggGU/S1g/1D2CRCXqP+3u7vX7iA==
+"@babel/plugin-proposal-export-namespace-from@^7.12.13":
+  version "7.12.13"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.12.13.tgz#393be47a4acd03fa2af6e3cde9b06e33de1b446d"
+  integrity sha512-INAgtFo4OnLN3Y/j0VwAgw3HDXcDtX+C/erMvWzuV9v71r7urb6iyMXu7eM9IgLr1ElLlOkaHjJ0SbCmdOQ3Iw==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.10.1"
-    "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.0"
+    "@babel/helper-plugin-utils" "^7.12.13"
+    "@babel/plugin-syntax-export-namespace-from" "^7.8.3"
 
-"@babel/plugin-proposal-numeric-separator@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.10.1.tgz#a9a38bc34f78bdfd981e791c27c6fdcec478c123"
-  integrity sha512-jjfym4N9HtCiNfyyLAVD8WqPYeHUrw4ihxuAynWj6zzp2gf9Ey2f7ImhFm6ikB3CLf5Z/zmcJDri6B4+9j9RsA==
+"@babel/plugin-proposal-json-strings@^7.13.8":
+  version "7.13.8"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.13.8.tgz#bf1fb362547075afda3634ed31571c5901afef7b"
+  integrity sha512-w4zOPKUFPX1mgvTmL/fcEqy34hrQ1CRcGxdphBc6snDnnqJ47EZDIyop6IwXzAC8G916hsIuXB2ZMBCExC5k7Q==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.10.1"
-    "@babel/plugin-syntax-numeric-separator" "^7.10.1"
+    "@babel/helper-plugin-utils" "^7.13.0"
+    "@babel/plugin-syntax-json-strings" "^7.8.3"
 
-"@babel/plugin-proposal-object-rest-spread@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.10.1.tgz#cba44908ac9f142650b4a65b8aa06bf3478d5fb6"
-  integrity sha512-Z+Qri55KiQkHh7Fc4BW6o+QBuTagbOp9txE+4U1i79u9oWlf2npkiDx+Rf3iK3lbcHBuNy9UOkwuR5wOMH3LIQ==
+"@babel/plugin-proposal-logical-assignment-operators@^7.13.8":
+  version "7.13.8"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.13.8.tgz#93fa78d63857c40ce3c8c3315220fd00bfbb4e1a"
+  integrity sha512-aul6znYB4N4HGweImqKn59Su9RS8lbUIqxtXTOcAGtNIDczoEFv+l1EhmX8rUBp3G1jMjKJm8m0jXVp63ZpS4A==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.10.1"
-    "@babel/plugin-syntax-object-rest-spread" "^7.8.0"
-    "@babel/plugin-transform-parameters" "^7.10.1"
+    "@babel/helper-plugin-utils" "^7.13.0"
+    "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4"
 
-"@babel/plugin-proposal-optional-catch-binding@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.10.1.tgz#c9f86d99305f9fa531b568ff5ab8c964b8b223d2"
-  integrity sha512-VqExgeE62YBqI3ogkGoOJp1R6u12DFZjqwJhqtKc2o5m1YTUuUWnos7bZQFBhwkxIFpWYJ7uB75U7VAPPiKETA==
+"@babel/plugin-proposal-nullish-coalescing-operator@^7.13.8":
+  version "7.13.8"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.13.8.tgz#3730a31dafd3c10d8ccd10648ed80a2ac5472ef3"
+  integrity sha512-iePlDPBn//UhxExyS9KyeYU7RM9WScAG+D3Hhno0PLJebAEpDZMocbDe64eqynhNAnwz/vZoL/q/QB2T1OH39A==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.10.1"
-    "@babel/plugin-syntax-optional-catch-binding" "^7.8.0"
+    "@babel/helper-plugin-utils" "^7.13.0"
+    "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3"
 
-"@babel/plugin-proposal-optional-chaining@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.10.1.tgz#15f5d6d22708629451a91be28f8facc55b0e818c"
-  integrity sha512-dqQj475q8+/avvok72CF3AOSV/SGEcH29zT5hhohqqvvZ2+boQoOr7iGldBG5YXTO2qgCgc2B3WvVLUdbeMlGA==
+"@babel/plugin-proposal-numeric-separator@^7.12.13":
+  version "7.12.13"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.12.13.tgz#bd9da3188e787b5120b4f9d465a8261ce67ed1db"
+  integrity sha512-O1jFia9R8BUCl3ZGB7eitaAPu62TXJRHn7rh+ojNERCFyqRwJMTmhz+tJ+k0CwI6CLjX/ee4qW74FSqlq9I35w==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.10.1"
-    "@babel/plugin-syntax-optional-chaining" "^7.8.0"
+    "@babel/helper-plugin-utils" "^7.12.13"
+    "@babel/plugin-syntax-numeric-separator" "^7.10.4"
 
-"@babel/plugin-proposal-private-methods@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.10.1.tgz#ed85e8058ab0fe309c3f448e5e1b73ca89cdb598"
-  integrity sha512-RZecFFJjDiQ2z6maFprLgrdnm0OzoC23Mx89xf1CcEsxmHuzuXOdniEuI+S3v7vjQG4F5sa6YtUp+19sZuSxHg==
+"@babel/plugin-proposal-object-rest-spread@^7.13.8":
+  version "7.13.8"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.13.8.tgz#5d210a4d727d6ce3b18f9de82cc99a3964eed60a"
+  integrity sha512-DhB2EuB1Ih7S3/IRX5AFVgZ16k3EzfRbq97CxAVI1KSYcW+lexV8VZb7G7L8zuPVSdQMRn0kiBpf/Yzu9ZKH0g==
   dependencies:
-    "@babel/helper-create-class-features-plugin" "^7.10.1"
-    "@babel/helper-plugin-utils" "^7.10.1"
+    "@babel/compat-data" "^7.13.8"
+    "@babel/helper-compilation-targets" "^7.13.8"
+    "@babel/helper-plugin-utils" "^7.13.0"
+    "@babel/plugin-syntax-object-rest-spread" "^7.8.3"
+    "@babel/plugin-transform-parameters" "^7.13.0"
 
-"@babel/plugin-proposal-unicode-property-regex@^7.10.1", "@babel/plugin-proposal-unicode-property-regex@^7.4.4":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.10.1.tgz#dc04feb25e2dd70c12b05d680190e138fa2c0c6f"
-  integrity sha512-JjfngYRvwmPwmnbRZyNiPFI8zxCZb8euzbCG/LxyKdeTb59tVciKo9GK9bi6JYKInk1H11Dq9j/zRqIH4KigfQ==
+"@babel/plugin-proposal-optional-catch-binding@^7.13.8":
+  version "7.13.8"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.13.8.tgz#3ad6bd5901506ea996fc31bdcf3ccfa2bed71107"
+  integrity sha512-0wS/4DUF1CuTmGo+NiaHfHcVSeSLj5S3e6RivPTg/2k3wOv3jO35tZ6/ZWsQhQMvdgI7CwphjQa/ccarLymHVA==
   dependencies:
-    "@babel/helper-create-regexp-features-plugin" "^7.10.1"
-    "@babel/helper-plugin-utils" "^7.10.1"
+    "@babel/helper-plugin-utils" "^7.13.0"
+    "@babel/plugin-syntax-optional-catch-binding" "^7.8.3"
 
-"@babel/plugin-syntax-async-generators@^7.8.0":
+"@babel/plugin-proposal-optional-chaining@^7.13.12":
+  version "7.13.12"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.13.12.tgz#ba9feb601d422e0adea6760c2bd6bbb7bfec4866"
+  integrity sha512-fcEdKOkIB7Tf4IxrgEVeFC4zeJSTr78no9wTdBuZZbqF64kzllU0ybo2zrzm7gUQfxGhBgq4E39oRs8Zx/RMYQ==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.13.0"
+    "@babel/helper-skip-transparent-expression-wrappers" "^7.12.1"
+    "@babel/plugin-syntax-optional-chaining" "^7.8.3"
+
+"@babel/plugin-proposal-private-methods@^7.13.0":
+  version "7.13.0"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.13.0.tgz#04bd4c6d40f6e6bbfa2f57e2d8094bad900ef787"
+  integrity sha512-MXyyKQd9inhx1kDYPkFRVOBXQ20ES8Pto3T7UZ92xj2mY0EVD8oAVzeyYuVfy/mxAdTSIayOvg+aVzcHV2bn6Q==
+  dependencies:
+    "@babel/helper-create-class-features-plugin" "^7.13.0"
+    "@babel/helper-plugin-utils" "^7.13.0"
+
+"@babel/plugin-proposal-unicode-property-regex@^7.12.13", "@babel/plugin-proposal-unicode-property-regex@^7.4.4":
+  version "7.12.13"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.12.13.tgz#bebde51339be829c17aaaaced18641deb62b39ba"
+  integrity sha512-XyJmZidNfofEkqFV5VC/bLabGmO5QzenPO/YOfGuEbgU+2sSwMmio3YLb4WtBgcmmdwZHyVyv8on77IUjQ5Gvg==
+  dependencies:
+    "@babel/helper-create-regexp-features-plugin" "^7.12.13"
+    "@babel/helper-plugin-utils" "^7.12.13"
+
+"@babel/plugin-syntax-async-generators@^7.8.4":
   version "7.8.4"
   resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d"
   integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==
   dependencies:
     "@babel/helper-plugin-utils" "^7.8.0"
 
-"@babel/plugin-syntax-class-properties@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.10.1.tgz#d5bc0645913df5b17ad7eda0fa2308330bde34c5"
-  integrity sha512-Gf2Yx/iRs1JREDtVZ56OrjjgFHCaldpTnuy9BHla10qyVT3YkIIGEtoDWhyop0ksu1GvNjHIoYRBqm3zoR1jyQ==
+"@babel/plugin-syntax-class-properties@^7.12.13":
+  version "7.12.13"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10"
+  integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.10.1"
+    "@babel/helper-plugin-utils" "^7.12.13"
 
-"@babel/plugin-syntax-dynamic-import@^7.8.0":
+"@babel/plugin-syntax-dynamic-import@^7.8.3":
   version "7.8.3"
   resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz#62bf98b2da3cd21d626154fc96ee5b3cb68eacb3"
   integrity sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==
   dependencies:
     "@babel/helper-plugin-utils" "^7.8.0"
 
-"@babel/plugin-syntax-json-strings@^7.8.0":
+"@babel/plugin-syntax-export-namespace-from@^7.8.3":
+  version "7.8.3"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz#028964a9ba80dbc094c915c487ad7c4e7a66465a"
+  integrity sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.8.3"
+
+"@babel/plugin-syntax-json-strings@^7.8.3":
   version "7.8.3"
   resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a"
   integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==
   dependencies:
     "@babel/helper-plugin-utils" "^7.8.0"
 
-"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.0":
+"@babel/plugin-syntax-logical-assignment-operators@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699"
+  integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.10.4"
+
+"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3":
   version "7.8.3"
   resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9"
   integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==
   dependencies:
     "@babel/helper-plugin-utils" "^7.8.0"
 
-"@babel/plugin-syntax-numeric-separator@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.1.tgz#25761ee7410bc8cf97327ba741ee94e4a61b7d99"
-  integrity sha512-uTd0OsHrpe3tH5gRPTxG8Voh99/WCU78vIm5NMRYPAqC8lR4vajt6KkCAknCHrx24vkPdd/05yfdGSB4EIY2mg==
+"@babel/plugin-syntax-numeric-separator@^7.10.4":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97"
+  integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.10.1"
+    "@babel/helper-plugin-utils" "^7.10.4"
 
-"@babel/plugin-syntax-object-rest-spread@^7.8.0":
+"@babel/plugin-syntax-object-rest-spread@^7.8.3":
   version "7.8.3"
   resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871"
   integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==
   dependencies:
     "@babel/helper-plugin-utils" "^7.8.0"
 
-"@babel/plugin-syntax-optional-catch-binding@^7.8.0":
+"@babel/plugin-syntax-optional-catch-binding@^7.8.3":
   version "7.8.3"
   resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1"
   integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==
   dependencies:
     "@babel/helper-plugin-utils" "^7.8.0"
 
-"@babel/plugin-syntax-optional-chaining@^7.8.0":
+"@babel/plugin-syntax-optional-chaining@^7.8.3":
   version "7.8.3"
   resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a"
   integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==
   dependencies:
     "@babel/helper-plugin-utils" "^7.8.0"
 
-"@babel/plugin-syntax-top-level-await@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.10.1.tgz#8b8733f8c57397b3eaa47ddba8841586dcaef362"
-  integrity sha512-hgA5RYkmZm8FTFT3yu2N9Bx7yVVOKYT6yEdXXo6j2JTm0wNxgqaGeQVaSHRjhfnQbX91DtjFB6McRFSlcJH3xQ==
+"@babel/plugin-syntax-top-level-await@^7.12.13":
+  version "7.12.13"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.12.13.tgz#c5f0fa6e249f5b739727f923540cf7a806130178"
+  integrity sha512-A81F9pDwyS7yM//KwbCSDqy3Uj4NMIurtplxphWxoYtNPov7cJsDkAFNNyVlIZ3jwGycVsurZ+LtOA8gZ376iQ==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.10.1"
+    "@babel/helper-plugin-utils" "^7.12.13"
 
-"@babel/plugin-syntax-typescript@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.10.1.tgz#5e82bc27bb4202b93b949b029e699db536733810"
-  integrity sha512-X/d8glkrAtra7CaQGMiGs/OGa6XgUzqPcBXCIGFCpCqnfGlT0Wfbzo/B89xHhnInTaItPK8LALblVXcUOEh95Q==
+"@babel/plugin-syntax-typescript@^7.12.13":
+  version "7.12.13"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.12.13.tgz#9dff111ca64154cef0f4dc52cf843d9f12ce4474"
+  integrity sha512-cHP3u1JiUiG2LFDKbXnwVad81GvfyIOmCD6HIEId6ojrY0Drfy2q1jw7BwN7dE84+kTnBjLkXoL3IEy/3JPu2w==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.10.1"
+    "@babel/helper-plugin-utils" "^7.12.13"
 
-"@babel/plugin-transform-arrow-functions@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.10.1.tgz#cb5ee3a36f0863c06ead0b409b4cc43a889b295b"
-  integrity sha512-6AZHgFJKP3DJX0eCNJj01RpytUa3SOGawIxweHkNX2L6PYikOZmoh5B0d7hIHaIgveMjX990IAa/xK7jRTN8OA==
+"@babel/plugin-transform-arrow-functions@^7.13.0":
+  version "7.13.0"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.13.0.tgz#10a59bebad52d637a027afa692e8d5ceff5e3dae"
+  integrity sha512-96lgJagobeVmazXFaDrbmCLQxBysKu7U6Do3mLsx27gf5Dk85ezysrs2BZUpXD703U/Su1xTBDxxar2oa4jAGg==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.10.1"
+    "@babel/helper-plugin-utils" "^7.13.0"
 
-"@babel/plugin-transform-async-to-generator@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.10.1.tgz#e5153eb1a3e028f79194ed8a7a4bf55f862b2062"
-  integrity sha512-XCgYjJ8TY2slj6SReBUyamJn3k2JLUIiiR5b6t1mNCMSvv7yx+jJpaewakikp0uWFQSF7ChPPoe3dHmXLpISkg==
+"@babel/plugin-transform-async-to-generator@^7.13.0":
+  version "7.13.0"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.13.0.tgz#8e112bf6771b82bf1e974e5e26806c5c99aa516f"
+  integrity sha512-3j6E004Dx0K3eGmhxVJxwwI89CTJrce7lg3UrtFuDAVQ/2+SJ/h/aSFOeE6/n0WB1GsOffsJp6MnPQNQ8nmwhg==
   dependencies:
-    "@babel/helper-module-imports" "^7.10.1"
-    "@babel/helper-plugin-utils" "^7.10.1"
-    "@babel/helper-remap-async-to-generator" "^7.10.1"
+    "@babel/helper-module-imports" "^7.12.13"
+    "@babel/helper-plugin-utils" "^7.13.0"
+    "@babel/helper-remap-async-to-generator" "^7.13.0"
 
-"@babel/plugin-transform-block-scoped-functions@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.10.1.tgz#146856e756d54b20fff14b819456b3e01820b85d"
-  integrity sha512-B7K15Xp8lv0sOJrdVAoukKlxP9N59HS48V1J3U/JGj+Ad+MHq+am6xJVs85AgXrQn4LV8vaYFOB+pr/yIuzW8Q==
+"@babel/plugin-transform-block-scoped-functions@^7.12.13":
+  version "7.12.13"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.12.13.tgz#a9bf1836f2a39b4eb6cf09967739de29ea4bf4c4"
+  integrity sha512-zNyFqbc3kI/fVpqwfqkg6RvBgFpC4J18aKKMmv7KdQ/1GgREapSJAykLMVNwfRGO3BtHj3YQZl8kxCXPcVMVeg==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.10.1"
+    "@babel/helper-plugin-utils" "^7.12.13"
 
-"@babel/plugin-transform-block-scoping@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.10.1.tgz#47092d89ca345811451cd0dc5d91605982705d5e"
-  integrity sha512-8bpWG6TtF5akdhIm/uWTyjHqENpy13Fx8chg7pFH875aNLwX8JxIxqm08gmAT+Whe6AOmaTeLPe7dpLbXt+xUw==
+"@babel/plugin-transform-block-scoping@^7.12.13":
+  version "7.12.13"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.12.13.tgz#f36e55076d06f41dfd78557ea039c1b581642e61"
+  integrity sha512-Pxwe0iqWJX4fOOM2kEZeUuAxHMWb9nK+9oh5d11bsLoB0xMg+mkDpt0eYuDZB7ETrY9bbcVlKUGTOGWy7BHsMQ==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.10.1"
-    lodash "^4.17.13"
+    "@babel/helper-plugin-utils" "^7.12.13"
 
-"@babel/plugin-transform-classes@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.10.1.tgz#6e11dd6c4dfae70f540480a4702477ed766d733f"
-  integrity sha512-P9V0YIh+ln/B3RStPoXpEQ/CoAxQIhRSUn7aXqQ+FZJ2u8+oCtjIXR3+X0vsSD8zv+mb56K7wZW1XiDTDGiDRQ==
+"@babel/plugin-transform-classes@^7.13.0":
+  version "7.13.0"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.13.0.tgz#0265155075c42918bf4d3a4053134176ad9b533b"
+  integrity sha512-9BtHCPUARyVH1oXGcSJD3YpsqRLROJx5ZNP6tN5vnk17N0SVf9WCtf8Nuh1CFmgByKKAIMstitKduoCmsaDK5g==
   dependencies:
-    "@babel/helper-annotate-as-pure" "^7.10.1"
-    "@babel/helper-define-map" "^7.10.1"
-    "@babel/helper-function-name" "^7.10.1"
-    "@babel/helper-optimise-call-expression" "^7.10.1"
-    "@babel/helper-plugin-utils" "^7.10.1"
-    "@babel/helper-replace-supers" "^7.10.1"
-    "@babel/helper-split-export-declaration" "^7.10.1"
+    "@babel/helper-annotate-as-pure" "^7.12.13"
+    "@babel/helper-function-name" "^7.12.13"
+    "@babel/helper-optimise-call-expression" "^7.12.13"
+    "@babel/helper-plugin-utils" "^7.13.0"
+    "@babel/helper-replace-supers" "^7.13.0"
+    "@babel/helper-split-export-declaration" "^7.12.13"
     globals "^11.1.0"
 
-"@babel/plugin-transform-computed-properties@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.10.1.tgz#59aa399064429d64dce5cf76ef9b90b7245ebd07"
-  integrity sha512-mqSrGjp3IefMsXIenBfGcPXxJxweQe2hEIwMQvjtiDQ9b1IBvDUjkAtV/HMXX47/vXf14qDNedXsIiNd1FmkaQ==
+"@babel/plugin-transform-computed-properties@^7.13.0":
+  version "7.13.0"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.13.0.tgz#845c6e8b9bb55376b1fa0b92ef0bdc8ea06644ed"
+  integrity sha512-RRqTYTeZkZAz8WbieLTvKUEUxZlUTdmL5KGMyZj7FnMfLNKV4+r5549aORG/mgojRmFlQMJDUupwAMiF2Q7OUg==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.10.1"
+    "@babel/helper-plugin-utils" "^7.13.0"
 
-"@babel/plugin-transform-destructuring@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.10.1.tgz#abd58e51337815ca3a22a336b85f62b998e71907"
-  integrity sha512-V/nUc4yGWG71OhaTH705pU8ZSdM6c1KmmLP8ys59oOYbT7RpMYAR3MsVOt6OHL0WzG7BlTU076va9fjJyYzJMA==
+"@babel/plugin-transform-destructuring@^7.13.0":
+  version "7.13.0"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.13.0.tgz#c5dce270014d4e1ebb1d806116694c12b7028963"
+  integrity sha512-zym5em7tePoNT9s964c0/KU3JPPnuq7VhIxPRefJ4/s82cD+q1mgKfuGRDMCPL0HTyKz4dISuQlCusfgCJ86HA==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.10.1"
+    "@babel/helper-plugin-utils" "^7.13.0"
 
-"@babel/plugin-transform-dotall-regex@^7.10.1", "@babel/plugin-transform-dotall-regex@^7.4.4":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.10.1.tgz#920b9fec2d78bb57ebb64a644d5c2ba67cc104ee"
-  integrity sha512-19VIMsD1dp02RvduFUmfzj8uknaO3uiHHF0s3E1OHnVsNj8oge8EQ5RzHRbJjGSetRnkEuBYO7TG1M5kKjGLOA==
+"@babel/plugin-transform-dotall-regex@^7.12.13", "@babel/plugin-transform-dotall-regex@^7.4.4":
+  version "7.12.13"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.12.13.tgz#3f1601cc29905bfcb67f53910f197aeafebb25ad"
+  integrity sha512-foDrozE65ZFdUC2OfgeOCrEPTxdB3yjqxpXh8CH+ipd9CHd4s/iq81kcUpyH8ACGNEPdFqbtzfgzbT/ZGlbDeQ==
   dependencies:
-    "@babel/helper-create-regexp-features-plugin" "^7.10.1"
-    "@babel/helper-plugin-utils" "^7.10.1"
+    "@babel/helper-create-regexp-features-plugin" "^7.12.13"
+    "@babel/helper-plugin-utils" "^7.12.13"
 
-"@babel/plugin-transform-duplicate-keys@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.10.1.tgz#c900a793beb096bc9d4d0a9d0cde19518ffc83b9"
-  integrity sha512-wIEpkX4QvX8Mo9W6XF3EdGttrIPZWozHfEaDTU0WJD/TDnXMvdDh30mzUl/9qWhnf7naicYartcEfUghTCSNpA==
+"@babel/plugin-transform-duplicate-keys@^7.12.13":
+  version "7.12.13"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.12.13.tgz#6f06b87a8b803fd928e54b81c258f0a0033904de"
+  integrity sha512-NfADJiiHdhLBW3pulJlJI2NB0t4cci4WTZ8FtdIuNc2+8pslXdPtRRAEWqUY+m9kNOk2eRYbTAOipAxlrOcwwQ==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.10.1"
+    "@babel/helper-plugin-utils" "^7.12.13"
 
-"@babel/plugin-transform-exponentiation-operator@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.10.1.tgz#279c3116756a60dd6e6f5e488ba7957db9c59eb3"
-  integrity sha512-lr/przdAbpEA2BUzRvjXdEDLrArGRRPwbaF9rvayuHRvdQ7lUTTkZnhZrJ4LE2jvgMRFF4f0YuPQ20vhiPYxtA==
+"@babel/plugin-transform-exponentiation-operator@^7.12.13":
+  version "7.12.13"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.12.13.tgz#4d52390b9a273e651e4aba6aee49ef40e80cd0a1"
+  integrity sha512-fbUelkM1apvqez/yYx1/oICVnGo2KM5s63mhGylrmXUxK/IAXSIf87QIxVfZldWf4QsOafY6vV3bX8aMHSvNrA==
   dependencies:
-    "@babel/helper-builder-binary-assignment-operator-visitor" "^7.10.1"
-    "@babel/helper-plugin-utils" "^7.10.1"
+    "@babel/helper-builder-binary-assignment-operator-visitor" "^7.12.13"
+    "@babel/helper-plugin-utils" "^7.12.13"
 
-"@babel/plugin-transform-for-of@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.10.1.tgz#ff01119784eb0ee32258e8646157ba2501fcfda5"
-  integrity sha512-US8KCuxfQcn0LwSCMWMma8M2R5mAjJGsmoCBVwlMygvmDUMkTCykc84IqN1M7t+agSfOmLYTInLCHJM+RUoz+w==
+"@babel/plugin-transform-for-of@^7.13.0":
+  version "7.13.0"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.13.0.tgz#c799f881a8091ac26b54867a845c3e97d2696062"
+  integrity sha512-IHKT00mwUVYE0zzbkDgNRP6SRzvfGCYsOxIRz8KsiaaHCcT9BWIkO+H9QRJseHBLOGBZkHUdHiqj6r0POsdytg==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.10.1"
+    "@babel/helper-plugin-utils" "^7.13.0"
 
-"@babel/plugin-transform-function-name@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.10.1.tgz#4ed46fd6e1d8fde2a2ec7b03c66d853d2c92427d"
-  integrity sha512-//bsKsKFBJfGd65qSNNh1exBy5Y9gD9ZN+DvrJ8f7HXr4avE5POW6zB7Rj6VnqHV33+0vXWUwJT0wSHubiAQkw==
+"@babel/plugin-transform-function-name@^7.12.13":
+  version "7.12.13"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.12.13.tgz#bb024452f9aaed861d374c8e7a24252ce3a50051"
+  integrity sha512-6K7gZycG0cmIwwF7uMK/ZqeCikCGVBdyP2J5SKNCXO5EOHcqi+z7Jwf8AmyDNcBgxET8DrEtCt/mPKPyAzXyqQ==
   dependencies:
-    "@babel/helper-function-name" "^7.10.1"
-    "@babel/helper-plugin-utils" "^7.10.1"
+    "@babel/helper-function-name" "^7.12.13"
+    "@babel/helper-plugin-utils" "^7.12.13"
 
-"@babel/plugin-transform-literals@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.10.1.tgz#5794f8da82846b22e4e6631ea1658bce708eb46a"
-  integrity sha512-qi0+5qgevz1NHLZroObRm5A+8JJtibb7vdcPQF1KQE12+Y/xxl8coJ+TpPW9iRq+Mhw/NKLjm+5SHtAHCC7lAw==
+"@babel/plugin-transform-literals@^7.12.13":
+  version "7.12.13"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.12.13.tgz#2ca45bafe4a820197cf315794a4d26560fe4bdb9"
+  integrity sha512-FW+WPjSR7hiUxMcKqyNjP05tQ2kmBCdpEpZHY1ARm96tGQCCBvXKnpjILtDplUnJ/eHZ0lALLM+d2lMFSpYJrQ==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.10.1"
+    "@babel/helper-plugin-utils" "^7.12.13"
 
-"@babel/plugin-transform-member-expression-literals@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.10.1.tgz#90347cba31bca6f394b3f7bd95d2bbfd9fce2f39"
-  integrity sha512-UmaWhDokOFT2GcgU6MkHC11i0NQcL63iqeufXWfRy6pUOGYeCGEKhvfFO6Vz70UfYJYHwveg62GS83Rvpxn+NA==
+"@babel/plugin-transform-member-expression-literals@^7.12.13":
+  version "7.12.13"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.12.13.tgz#5ffa66cd59b9e191314c9f1f803b938e8c081e40"
+  integrity sha512-kxLkOsg8yir4YeEPHLuO2tXP9R/gTjpuTOjshqSpELUN3ZAg2jfDnKUvzzJxObun38sw3wm4Uu69sX/zA7iRvg==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.10.1"
+    "@babel/helper-plugin-utils" "^7.12.13"
 
-"@babel/plugin-transform-modules-amd@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.10.1.tgz#65950e8e05797ebd2fe532b96e19fc5482a1d52a"
-  integrity sha512-31+hnWSFRI4/ACFr1qkboBbrTxoBIzj7qA69qlq8HY8p7+YCzkCT6/TvQ1a4B0z27VeWtAeJd6pr5G04dc1iHw==
+"@babel/plugin-transform-modules-amd@^7.13.0":
+  version "7.13.0"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.13.0.tgz#19f511d60e3d8753cc5a6d4e775d3a5184866cc3"
+  integrity sha512-EKy/E2NHhY/6Vw5d1k3rgoobftcNUmp9fGjb9XZwQLtTctsRBOTRO7RHHxfIky1ogMN5BxN7p9uMA3SzPfotMQ==
   dependencies:
-    "@babel/helper-module-transforms" "^7.10.1"
-    "@babel/helper-plugin-utils" "^7.10.1"
+    "@babel/helper-module-transforms" "^7.13.0"
+    "@babel/helper-plugin-utils" "^7.13.0"
     babel-plugin-dynamic-import-node "^2.3.3"
 
-"@babel/plugin-transform-modules-commonjs@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.10.1.tgz#d5ff4b4413ed97ffded99961056e1fb980fb9301"
-  integrity sha512-AQG4fc3KOah0vdITwt7Gi6hD9BtQP/8bhem7OjbaMoRNCH5Djx42O2vYMfau7QnAzQCa+RJnhJBmFFMGpQEzrg==
+"@babel/plugin-transform-modules-commonjs@^7.13.8":
+  version "7.13.8"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.13.8.tgz#7b01ad7c2dcf2275b06fa1781e00d13d420b3e1b"
+  integrity sha512-9QiOx4MEGglfYZ4XOnU79OHr6vIWUakIj9b4mioN8eQIoEh+pf5p/zEB36JpDFWA12nNMiRf7bfoRvl9Rn79Bw==
   dependencies:
-    "@babel/helper-module-transforms" "^7.10.1"
-    "@babel/helper-plugin-utils" "^7.10.1"
-    "@babel/helper-simple-access" "^7.10.1"
+    "@babel/helper-module-transforms" "^7.13.0"
+    "@babel/helper-plugin-utils" "^7.13.0"
+    "@babel/helper-simple-access" "^7.12.13"
     babel-plugin-dynamic-import-node "^2.3.3"
 
-"@babel/plugin-transform-modules-systemjs@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.10.1.tgz#9962e4b0ac6aaf2e20431ada3d8ec72082cbffb6"
-  integrity sha512-ewNKcj1TQZDL3YnO85qh9zo1YF1CHgmSTlRQgHqe63oTrMI85cthKtZjAiZSsSNjPQ5NCaYo5QkbYqEw1ZBgZA==
+"@babel/plugin-transform-modules-systemjs@^7.13.8":
+  version "7.13.8"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.13.8.tgz#6d066ee2bff3c7b3d60bf28dec169ad993831ae3"
+  integrity sha512-hwqctPYjhM6cWvVIlOIe27jCIBgHCsdH2xCJVAYQm7V5yTMoilbVMi9f6wKg0rpQAOn6ZG4AOyvCqFF/hUh6+A==
   dependencies:
-    "@babel/helper-hoist-variables" "^7.10.1"
-    "@babel/helper-module-transforms" "^7.10.1"
-    "@babel/helper-plugin-utils" "^7.10.1"
+    "@babel/helper-hoist-variables" "^7.13.0"
+    "@babel/helper-module-transforms" "^7.13.0"
+    "@babel/helper-plugin-utils" "^7.13.0"
+    "@babel/helper-validator-identifier" "^7.12.11"
     babel-plugin-dynamic-import-node "^2.3.3"
 
-"@babel/plugin-transform-modules-umd@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.10.1.tgz#ea080911ffc6eb21840a5197a39ede4ee67b1595"
-  integrity sha512-EIuiRNMd6GB6ulcYlETnYYfgv4AxqrswghmBRQbWLHZxN4s7mupxzglnHqk9ZiUpDI4eRWewedJJNj67PWOXKA==
+"@babel/plugin-transform-modules-umd@^7.13.0":
+  version "7.13.0"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.13.0.tgz#8a3d96a97d199705b9fd021580082af81c06e70b"
+  integrity sha512-D/ILzAh6uyvkWjKKyFE/W0FzWwasv6vPTSqPcjxFqn6QpX3u8DjRVliq4F2BamO2Wee/om06Vyy+vPkNrd4wxw==
   dependencies:
-    "@babel/helper-module-transforms" "^7.10.1"
-    "@babel/helper-plugin-utils" "^7.10.1"
+    "@babel/helper-module-transforms" "^7.13.0"
+    "@babel/helper-plugin-utils" "^7.13.0"
 
-"@babel/plugin-transform-named-capturing-groups-regex@^7.8.3":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.8.3.tgz#a2a72bffa202ac0e2d0506afd0939c5ecbc48c6c"
-  integrity sha512-f+tF/8UVPU86TrCb06JoPWIdDpTNSGGcAtaD9mLP0aYGA0OS0j7j7DHJR0GTFrUZPUU6loZhbsVZgTh0N+Qdnw==
+"@babel/plugin-transform-named-capturing-groups-regex@^7.12.13":
+  version "7.12.13"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.12.13.tgz#2213725a5f5bbbe364b50c3ba5998c9599c5c9d9"
+  integrity sha512-Xsm8P2hr5hAxyYblrfACXpQKdQbx4m2df9/ZZSQ8MAhsadw06+jW7s9zsSw6he+mJZXRlVMyEnVktJo4zjk1WA==
   dependencies:
-    "@babel/helper-create-regexp-features-plugin" "^7.8.3"
+    "@babel/helper-create-regexp-features-plugin" "^7.12.13"
 
-"@babel/plugin-transform-new-target@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.10.1.tgz#6ee41a5e648da7632e22b6fb54012e87f612f324"
-  integrity sha512-MBlzPc1nJvbmO9rPr1fQwXOM2iGut+JC92ku6PbiJMMK7SnQc1rytgpopveE3Evn47gzvGYeCdgfCDbZo0ecUw==
+"@babel/plugin-transform-new-target@^7.12.13":
+  version "7.12.13"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.12.13.tgz#e22d8c3af24b150dd528cbd6e685e799bf1c351c"
+  integrity sha512-/KY2hbLxrG5GTQ9zzZSc3xWiOy379pIETEhbtzwZcw9rvuaVV4Fqy7BYGYOWZnaoXIQYbbJ0ziXLa/sKcGCYEQ==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.10.1"
+    "@babel/helper-plugin-utils" "^7.12.13"
 
-"@babel/plugin-transform-object-super@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.10.1.tgz#2e3016b0adbf262983bf0d5121d676a5ed9c4fde"
-  integrity sha512-WnnStUDN5GL+wGQrJylrnnVlFhFmeArINIR9gjhSeYyvroGhBrSAXYg/RHsnfzmsa+onJrTJrEClPzgNmmQ4Gw==
+"@babel/plugin-transform-object-super@^7.12.13":
+  version "7.12.13"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.12.13.tgz#b4416a2d63b8f7be314f3d349bd55a9c1b5171f7"
+  integrity sha512-JzYIcj3XtYspZDV8j9ulnoMPZZnF/Cj0LUxPOjR89BdBVx+zYJI9MdMIlUZjbXDX+6YVeS6I3e8op+qQ3BYBoQ==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.10.1"
-    "@babel/helper-replace-supers" "^7.10.1"
+    "@babel/helper-plugin-utils" "^7.12.13"
+    "@babel/helper-replace-supers" "^7.12.13"
 
-"@babel/plugin-transform-parameters@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.10.1.tgz#b25938a3c5fae0354144a720b07b32766f683ddd"
-  integrity sha512-tJ1T0n6g4dXMsL45YsSzzSDZCxiHXAQp/qHrucOq5gEHncTA3xDxnd5+sZcoQp+N1ZbieAaB8r/VUCG0gqseOg==
+"@babel/plugin-transform-parameters@^7.13.0":
+  version "7.13.0"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.13.0.tgz#8fa7603e3097f9c0b7ca1a4821bc2fb52e9e5007"
+  integrity sha512-Jt8k/h/mIwE2JFEOb3lURoY5C85ETcYPnbuAJ96zRBzh1XHtQZfs62ChZ6EP22QlC8c7Xqr9q+e1SU5qttwwjw==
   dependencies:
-    "@babel/helper-get-function-arity" "^7.10.1"
-    "@babel/helper-plugin-utils" "^7.10.1"
+    "@babel/helper-plugin-utils" "^7.13.0"
 
-"@babel/plugin-transform-property-literals@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.10.1.tgz#cffc7315219230ed81dc53e4625bf86815b6050d"
-  integrity sha512-Kr6+mgag8auNrgEpbfIWzdXYOvqDHZOF0+Bx2xh4H2EDNwcbRb9lY6nkZg8oSjsX+DH9Ebxm9hOqtKW+gRDeNA==
+"@babel/plugin-transform-property-literals@^7.12.13":
+  version "7.12.13"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.12.13.tgz#4e6a9e37864d8f1b3bc0e2dce7bf8857db8b1a81"
+  integrity sha512-nqVigwVan+lR+g8Fj8Exl0UQX2kymtjcWfMOYM1vTYEKujeyv2SkMgazf2qNcK7l4SDiKyTA/nHCPqL4e2zo1A==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.10.1"
+    "@babel/helper-plugin-utils" "^7.12.13"
 
-"@babel/plugin-transform-regenerator@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.10.1.tgz#10e175cbe7bdb63cc9b39f9b3f823c5c7c5c5490"
-  integrity sha512-B3+Y2prScgJ2Bh/2l9LJxKbb8C8kRfsG4AdPT+n7ixBHIxJaIG8bi8tgjxUMege1+WqSJ+7gu1YeoMVO3gPWzw==
+"@babel/plugin-transform-regenerator@^7.12.13":
+  version "7.12.13"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.12.13.tgz#b628bcc9c85260ac1aeb05b45bde25210194a2f5"
+  integrity sha512-lxb2ZAvSLyJ2PEe47hoGWPmW22v7CtSl9jW8mingV4H2sEX/JOcrAj2nPuGWi56ERUm2bUpjKzONAuT6HCn2EA==
   dependencies:
     regenerator-transform "^0.14.2"
 
-"@babel/plugin-transform-reserved-words@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.10.1.tgz#0fc1027312b4d1c3276a57890c8ae3bcc0b64a86"
-  integrity sha512-qN1OMoE2nuqSPmpTqEM7OvJ1FkMEV+BjVeZZm9V9mq/x1JLKQ4pcv8riZJMNN3u2AUGl0ouOMjRr2siecvHqUQ==
+"@babel/plugin-transform-reserved-words@^7.12.13":
+  version "7.12.13"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.12.13.tgz#7d9988d4f06e0fe697ea1d9803188aa18b472695"
+  integrity sha512-xhUPzDXxZN1QfiOy/I5tyye+TRz6lA7z6xaT4CLOjPRMVg1ldRf0LHw0TDBpYL4vG78556WuHdyO9oi5UmzZBg==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.10.1"
+    "@babel/helper-plugin-utils" "^7.12.13"
 
-"@babel/plugin-transform-runtime@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.10.1.tgz#fd1887f749637fb2ed86dc278e79eb41df37f4b1"
-  integrity sha512-4w2tcglDVEwXJ5qxsY++DgWQdNJcCCsPxfT34wCUwIf2E7dI7pMpH8JczkMBbgBTNzBX62SZlNJ9H+De6Zebaw==
+"@babel/plugin-transform-runtime@^7.13.10":
+  version "7.13.10"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.13.10.tgz#a1e40d22e2bf570c591c9c7e5ab42d6bf1e419e1"
+  integrity sha512-Y5k8ipgfvz5d/76tx7JYbKQTcgFSU6VgJ3kKQv4zGTKr+a9T/KBvfRvGtSFgKDQGt/DBykQixV0vNWKIdzWErA==
   dependencies:
-    "@babel/helper-module-imports" "^7.10.1"
-    "@babel/helper-plugin-utils" "^7.10.1"
-    resolve "^1.8.1"
-    semver "^5.5.1"
+    "@babel/helper-module-imports" "^7.12.13"
+    "@babel/helper-plugin-utils" "^7.13.0"
+    babel-plugin-polyfill-corejs2 "^0.1.4"
+    babel-plugin-polyfill-corejs3 "^0.1.3"
+    babel-plugin-polyfill-regenerator "^0.1.2"
+    semver "^6.3.0"
 
-"@babel/plugin-transform-shorthand-properties@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.10.1.tgz#e8b54f238a1ccbae482c4dce946180ae7b3143f3"
-  integrity sha512-AR0E/lZMfLstScFwztApGeyTHJ5u3JUKMjneqRItWeEqDdHWZwAOKycvQNCasCK/3r5YXsuNG25funcJDu7Y2g==
+"@babel/plugin-transform-shorthand-properties@^7.12.13":
+  version "7.12.13"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.12.13.tgz#db755732b70c539d504c6390d9ce90fe64aff7ad"
+  integrity sha512-xpL49pqPnLtf0tVluuqvzWIgLEhuPpZzvs2yabUHSKRNlN7ScYU7aMlmavOeyXJZKgZKQRBlh8rHbKiJDraTSw==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.10.1"
+    "@babel/helper-plugin-utils" "^7.12.13"
 
-"@babel/plugin-transform-spread@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.10.1.tgz#0c6d618a0c4461a274418460a28c9ccf5239a7c8"
-  integrity sha512-8wTPym6edIrClW8FI2IoaePB91ETOtg36dOkj3bYcNe7aDMN2FXEoUa+WrmPc4xa1u2PQK46fUX2aCb+zo9rfw==
+"@babel/plugin-transform-spread@^7.13.0":
+  version "7.13.0"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.13.0.tgz#84887710e273c1815ace7ae459f6f42a5d31d5fd"
+  integrity sha512-V6vkiXijjzYeFmQTr3dBxPtZYLPcUfY34DebOU27jIl2M/Y8Egm52Hw82CSjjPqd54GTlJs5x+CR7HeNr24ckg==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.10.1"
+    "@babel/helper-plugin-utils" "^7.13.0"
+    "@babel/helper-skip-transparent-expression-wrappers" "^7.12.1"
 
-"@babel/plugin-transform-sticky-regex@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.10.1.tgz#90fc89b7526228bed9842cff3588270a7a393b00"
-  integrity sha512-j17ojftKjrL7ufX8ajKvwRilwqTok4q+BjkknmQw9VNHnItTyMP5anPFzxFJdCQs7clLcWpCV3ma+6qZWLnGMA==
+"@babel/plugin-transform-sticky-regex@^7.12.13":
+  version "7.12.13"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.12.13.tgz#760ffd936face73f860ae646fb86ee82f3d06d1f"
+  integrity sha512-Jc3JSaaWT8+fr7GRvQP02fKDsYk4K/lYwWq38r/UGfaxo89ajud321NH28KRQ7xy1Ybc0VUE5Pz8psjNNDUglg==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.10.1"
-    "@babel/helper-regex" "^7.10.1"
+    "@babel/helper-plugin-utils" "^7.12.13"
 
-"@babel/plugin-transform-template-literals@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.10.1.tgz#914c7b7f4752c570ea00553b4284dad8070e8628"
-  integrity sha512-t7B/3MQf5M1T9hPCRG28DNGZUuxAuDqLYS03rJrIk2prj/UV7Z6FOneijhQhnv/Xa039vidXeVbvjK2SK5f7Gg==
+"@babel/plugin-transform-template-literals@^7.13.0":
+  version "7.13.0"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.13.0.tgz#a36049127977ad94438dee7443598d1cefdf409d"
+  integrity sha512-d67umW6nlfmr1iehCcBv69eSUSySk1EsIS8aTDX4Xo9qajAh6mYtcl4kJrBkGXuxZPEgVr7RVfAvNW6YQkd4Mw==
   dependencies:
-    "@babel/helper-annotate-as-pure" "^7.10.1"
-    "@babel/helper-plugin-utils" "^7.10.1"
+    "@babel/helper-plugin-utils" "^7.13.0"
 
-"@babel/plugin-transform-typeof-symbol@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.10.1.tgz#60c0239b69965d166b80a84de7315c1bc7e0bb0e"
-  integrity sha512-qX8KZcmbvA23zDi+lk9s6hC1FM7jgLHYIjuLgULgc8QtYnmB3tAVIYkNoKRQ75qWBeyzcoMoK8ZQmogGtC/w0g==
+"@babel/plugin-transform-typeof-symbol@^7.12.13":
+  version "7.12.13"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.12.13.tgz#785dd67a1f2ea579d9c2be722de8c84cb85f5a7f"
+  integrity sha512-eKv/LmUJpMnu4npgfvs3LiHhJua5fo/CysENxa45YCQXZwKnGCQKAg87bvoqSW1fFT+HA32l03Qxsm8ouTY3ZQ==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.10.1"
+    "@babel/helper-plugin-utils" "^7.12.13"
 
-"@babel/plugin-transform-typescript@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.10.1.tgz#2c54daea231f602468686d9faa76f182a94507a6"
-  integrity sha512-v+QWKlmCnsaimLeqq9vyCsVRMViZG1k2SZTlcZvB+TqyH570Zsij8nvVUZzOASCRiQFUxkLrn9Wg/kH0zgy5OQ==
+"@babel/plugin-transform-typescript@^7.13.0":
+  version "7.13.0"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.13.0.tgz#4a498e1f3600342d2a9e61f60131018f55774853"
+  integrity sha512-elQEwluzaU8R8dbVuW2Q2Y8Nznf7hnjM7+DSCd14Lo5fF63C9qNLbwZYbmZrtV9/ySpSUpkRpQXvJb6xyu4hCQ==
   dependencies:
-    "@babel/helper-create-class-features-plugin" "^7.10.1"
-    "@babel/helper-plugin-utils" "^7.10.1"
-    "@babel/plugin-syntax-typescript" "^7.10.1"
+    "@babel/helper-create-class-features-plugin" "^7.13.0"
+    "@babel/helper-plugin-utils" "^7.13.0"
+    "@babel/plugin-syntax-typescript" "^7.12.13"
 
-"@babel/plugin-transform-unicode-escapes@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.10.1.tgz#add0f8483dab60570d9e03cecef6c023aa8c9940"
-  integrity sha512-zZ0Poh/yy1d4jeDWpx/mNwbKJVwUYJX73q+gyh4bwtG0/iUlzdEu0sLMda8yuDFS6LBQlT/ST1SJAR6zYwXWgw==
+"@babel/plugin-transform-unicode-escapes@^7.12.13":
+  version "7.12.13"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.12.13.tgz#840ced3b816d3b5127dd1d12dcedc5dead1a5e74"
+  integrity sha512-0bHEkdwJ/sN/ikBHfSmOXPypN/beiGqjo+o4/5K+vxEFNPRPdImhviPakMKG4x96l85emoa0Z6cDflsdBusZbw==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.10.1"
+    "@babel/helper-plugin-utils" "^7.12.13"
 
-"@babel/plugin-transform-unicode-regex@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.10.1.tgz#6b58f2aea7b68df37ac5025d9c88752443a6b43f"
-  integrity sha512-Y/2a2W299k0VIUdbqYm9X2qS6fE0CUBhhiPpimK6byy7OJ/kORLlIX+J6UrjgNu5awvs62k+6RSslxhcvVw2Tw==
+"@babel/plugin-transform-unicode-regex@^7.12.13":
+  version "7.12.13"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.12.13.tgz#b52521685804e155b1202e83fc188d34bb70f5ac"
+  integrity sha512-mDRzSNY7/zopwisPZ5kM9XKCfhchqIYwAKRERtEnhYscZB79VRekuRSoYbN0+KVe3y8+q1h6A4svXtP7N+UoCA==
   dependencies:
-    "@babel/helper-create-regexp-features-plugin" "^7.10.1"
-    "@babel/helper-plugin-utils" "^7.10.1"
+    "@babel/helper-create-regexp-features-plugin" "^7.12.13"
+    "@babel/helper-plugin-utils" "^7.12.13"
 
-"@babel/preset-env@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.10.1.tgz#099e1b76379739bdcbfab3d548dc7e7edb2ac808"
-  integrity sha512-bGWNfjfXRLnqbN2T4lB3pMfoic8dkRrmHpVZamSFHzGy5xklyHTobZ28TVUD2grhE5WDnu67tBj8oslIhkiOMQ==
+"@babel/preset-env@^7.13.12":
+  version "7.13.12"
+  resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.13.12.tgz#6dff470478290582ac282fb77780eadf32480237"
+  integrity sha512-JzElc6jk3Ko6zuZgBtjOd01pf9yYDEIH8BcqVuYIuOkzOwDesoa/Nz4gIo4lBG6K861KTV9TvIgmFuT6ytOaAA==
   dependencies:
-    "@babel/compat-data" "^7.10.1"
-    "@babel/helper-compilation-targets" "^7.10.1"
-    "@babel/helper-module-imports" "^7.10.1"
-    "@babel/helper-plugin-utils" "^7.10.1"
-    "@babel/plugin-proposal-async-generator-functions" "^7.10.1"
-    "@babel/plugin-proposal-class-properties" "^7.10.1"
-    "@babel/plugin-proposal-dynamic-import" "^7.10.1"
-    "@babel/plugin-proposal-json-strings" "^7.10.1"
-    "@babel/plugin-proposal-nullish-coalescing-operator" "^7.10.1"
-    "@babel/plugin-proposal-numeric-separator" "^7.10.1"
-    "@babel/plugin-proposal-object-rest-spread" "^7.10.1"
-    "@babel/plugin-proposal-optional-catch-binding" "^7.10.1"
-    "@babel/plugin-proposal-optional-chaining" "^7.10.1"
-    "@babel/plugin-proposal-private-methods" "^7.10.1"
-    "@babel/plugin-proposal-unicode-property-regex" "^7.10.1"
-    "@babel/plugin-syntax-async-generators" "^7.8.0"
-    "@babel/plugin-syntax-class-properties" "^7.10.1"
-    "@babel/plugin-syntax-dynamic-import" "^7.8.0"
-    "@babel/plugin-syntax-json-strings" "^7.8.0"
-    "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.0"
-    "@babel/plugin-syntax-numeric-separator" "^7.10.1"
-    "@babel/plugin-syntax-object-rest-spread" "^7.8.0"
-    "@babel/plugin-syntax-optional-catch-binding" "^7.8.0"
-    "@babel/plugin-syntax-optional-chaining" "^7.8.0"
-    "@babel/plugin-syntax-top-level-await" "^7.10.1"
-    "@babel/plugin-transform-arrow-functions" "^7.10.1"
-    "@babel/plugin-transform-async-to-generator" "^7.10.1"
-    "@babel/plugin-transform-block-scoped-functions" "^7.10.1"
-    "@babel/plugin-transform-block-scoping" "^7.10.1"
-    "@babel/plugin-transform-classes" "^7.10.1"
-    "@babel/plugin-transform-computed-properties" "^7.10.1"
-    "@babel/plugin-transform-destructuring" "^7.10.1"
-    "@babel/plugin-transform-dotall-regex" "^7.10.1"
-    "@babel/plugin-transform-duplicate-keys" "^7.10.1"
-    "@babel/plugin-transform-exponentiation-operator" "^7.10.1"
-    "@babel/plugin-transform-for-of" "^7.10.1"
-    "@babel/plugin-transform-function-name" "^7.10.1"
-    "@babel/plugin-transform-literals" "^7.10.1"
-    "@babel/plugin-transform-member-expression-literals" "^7.10.1"
-    "@babel/plugin-transform-modules-amd" "^7.10.1"
-    "@babel/plugin-transform-modules-commonjs" "^7.10.1"
-    "@babel/plugin-transform-modules-systemjs" "^7.10.1"
-    "@babel/plugin-transform-modules-umd" "^7.10.1"
-    "@babel/plugin-transform-named-capturing-groups-regex" "^7.8.3"
-    "@babel/plugin-transform-new-target" "^7.10.1"
-    "@babel/plugin-transform-object-super" "^7.10.1"
-    "@babel/plugin-transform-parameters" "^7.10.1"
-    "@babel/plugin-transform-property-literals" "^7.10.1"
-    "@babel/plugin-transform-regenerator" "^7.10.1"
-    "@babel/plugin-transform-reserved-words" "^7.10.1"
-    "@babel/plugin-transform-shorthand-properties" "^7.10.1"
-    "@babel/plugin-transform-spread" "^7.10.1"
-    "@babel/plugin-transform-sticky-regex" "^7.10.1"
-    "@babel/plugin-transform-template-literals" "^7.10.1"
-    "@babel/plugin-transform-typeof-symbol" "^7.10.1"
-    "@babel/plugin-transform-unicode-escapes" "^7.10.1"
-    "@babel/plugin-transform-unicode-regex" "^7.10.1"
-    "@babel/preset-modules" "^0.1.3"
-    "@babel/types" "^7.10.1"
-    browserslist "^4.12.0"
-    core-js-compat "^3.6.2"
-    invariant "^2.2.2"
-    levenary "^1.1.1"
-    semver "^5.5.0"
+    "@babel/compat-data" "^7.13.12"
+    "@babel/helper-compilation-targets" "^7.13.10"
+    "@babel/helper-plugin-utils" "^7.13.0"
+    "@babel/helper-validator-option" "^7.12.17"
+    "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.13.12"
+    "@babel/plugin-proposal-async-generator-functions" "^7.13.8"
+    "@babel/plugin-proposal-class-properties" "^7.13.0"
+    "@babel/plugin-proposal-dynamic-import" "^7.13.8"
+    "@babel/plugin-proposal-export-namespace-from" "^7.12.13"
+    "@babel/plugin-proposal-json-strings" "^7.13.8"
+    "@babel/plugin-proposal-logical-assignment-operators" "^7.13.8"
+    "@babel/plugin-proposal-nullish-coalescing-operator" "^7.13.8"
+    "@babel/plugin-proposal-numeric-separator" "^7.12.13"
+    "@babel/plugin-proposal-object-rest-spread" "^7.13.8"
+    "@babel/plugin-proposal-optional-catch-binding" "^7.13.8"
+    "@babel/plugin-proposal-optional-chaining" "^7.13.12"
+    "@babel/plugin-proposal-private-methods" "^7.13.0"
+    "@babel/plugin-proposal-unicode-property-regex" "^7.12.13"
+    "@babel/plugin-syntax-async-generators" "^7.8.4"
+    "@babel/plugin-syntax-class-properties" "^7.12.13"
+    "@babel/plugin-syntax-dynamic-import" "^7.8.3"
+    "@babel/plugin-syntax-export-namespace-from" "^7.8.3"
+    "@babel/plugin-syntax-json-strings" "^7.8.3"
+    "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4"
+    "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3"
+    "@babel/plugin-syntax-numeric-separator" "^7.10.4"
+    "@babel/plugin-syntax-object-rest-spread" "^7.8.3"
+    "@babel/plugin-syntax-optional-catch-binding" "^7.8.3"
+    "@babel/plugin-syntax-optional-chaining" "^7.8.3"
+    "@babel/plugin-syntax-top-level-await" "^7.12.13"
+    "@babel/plugin-transform-arrow-functions" "^7.13.0"
+    "@babel/plugin-transform-async-to-generator" "^7.13.0"
+    "@babel/plugin-transform-block-scoped-functions" "^7.12.13"
+    "@babel/plugin-transform-block-scoping" "^7.12.13"
+    "@babel/plugin-transform-classes" "^7.13.0"
+    "@babel/plugin-transform-computed-properties" "^7.13.0"
+    "@babel/plugin-transform-destructuring" "^7.13.0"
+    "@babel/plugin-transform-dotall-regex" "^7.12.13"
+    "@babel/plugin-transform-duplicate-keys" "^7.12.13"
+    "@babel/plugin-transform-exponentiation-operator" "^7.12.13"
+    "@babel/plugin-transform-for-of" "^7.13.0"
+    "@babel/plugin-transform-function-name" "^7.12.13"
+    "@babel/plugin-transform-literals" "^7.12.13"
+    "@babel/plugin-transform-member-expression-literals" "^7.12.13"
+    "@babel/plugin-transform-modules-amd" "^7.13.0"
+    "@babel/plugin-transform-modules-commonjs" "^7.13.8"
+    "@babel/plugin-transform-modules-systemjs" "^7.13.8"
+    "@babel/plugin-transform-modules-umd" "^7.13.0"
+    "@babel/plugin-transform-named-capturing-groups-regex" "^7.12.13"
+    "@babel/plugin-transform-new-target" "^7.12.13"
+    "@babel/plugin-transform-object-super" "^7.12.13"
+    "@babel/plugin-transform-parameters" "^7.13.0"
+    "@babel/plugin-transform-property-literals" "^7.12.13"
+    "@babel/plugin-transform-regenerator" "^7.12.13"
+    "@babel/plugin-transform-reserved-words" "^7.12.13"
+    "@babel/plugin-transform-shorthand-properties" "^7.12.13"
+    "@babel/plugin-transform-spread" "^7.13.0"
+    "@babel/plugin-transform-sticky-regex" "^7.12.13"
+    "@babel/plugin-transform-template-literals" "^7.13.0"
+    "@babel/plugin-transform-typeof-symbol" "^7.12.13"
+    "@babel/plugin-transform-unicode-escapes" "^7.12.13"
+    "@babel/plugin-transform-unicode-regex" "^7.12.13"
+    "@babel/preset-modules" "^0.1.4"
+    "@babel/types" "^7.13.12"
+    babel-plugin-polyfill-corejs2 "^0.1.4"
+    babel-plugin-polyfill-corejs3 "^0.1.3"
+    babel-plugin-polyfill-regenerator "^0.1.2"
+    core-js-compat "^3.9.0"
+    semver "^6.3.0"
 
-"@babel/preset-modules@^0.1.3":
-  version "0.1.3"
-  resolved "https://registry.yarnpkg.com/@babel/preset-modules/-/preset-modules-0.1.3.tgz#13242b53b5ef8c883c3cf7dddd55b36ce80fbc72"
-  integrity sha512-Ra3JXOHBq2xd56xSF7lMKXdjBn3T772Y1Wet3yWnkDly9zHvJki029tAFzvAAK5cf4YV3yoxuP61crYRol6SVg==
+"@babel/preset-modules@^0.1.4":
+  version "0.1.4"
+  resolved "https://registry.yarnpkg.com/@babel/preset-modules/-/preset-modules-0.1.4.tgz#362f2b68c662842970fdb5e254ffc8fc1c2e415e"
+  integrity sha512-J36NhwnfdzpmH41M1DrnkkgAqhZaqr/NBdPfQ677mLzlaXo+oDiv1deyCDtgAhz8p328otdob0Du7+xgHGZbKg==
   dependencies:
     "@babel/helper-plugin-utils" "^7.0.0"
     "@babel/plugin-proposal-unicode-property-regex" "^7.4.4"
@@ -796,29 +837,30 @@
     "@babel/types" "^7.4.4"
     esutils "^2.0.2"
 
-"@babel/preset-typescript@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.10.1.tgz#a8d8d9035f55b7d99a2461a0bdc506582914d07e"
-  integrity sha512-m6GV3y1ShiqxnyQj10600ZVOFrSSAa8HQ3qIUk2r+gcGtHTIRw0dJnFLt1WNXpKjtVw7yw1DAPU/6ma2ZvgJuA==
+"@babel/preset-typescript@^7.13.0":
+  version "7.13.0"
+  resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.13.0.tgz#ab107e5f050609d806fbb039bec553b33462c60a"
+  integrity sha512-LXJwxrHy0N3f6gIJlYbLta1D9BDtHpQeqwzM0LIfjDlr6UE/D5Mc7W4iDiQzaE+ks0sTjT26ArcHWnJVt0QiHw==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.10.1"
-    "@babel/plugin-transform-typescript" "^7.10.1"
+    "@babel/helper-plugin-utils" "^7.13.0"
+    "@babel/helper-validator-option" "^7.12.17"
+    "@babel/plugin-transform-typescript" "^7.13.0"
 
-"@babel/register@^7.10.1":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/register/-/register-7.10.1.tgz#b6567c5cb5049f44bbf8c35d6ff68ca3c43238ed"
-  integrity sha512-sl96+kB3IA2B9EzpwwBmYadOT14vw3KaXOknGDbJaZCOj52GDA4Tivudq9doCJcB+bEIKCEARZYwRgBBsCGXyg==
+"@babel/register@^7.13.14":
+  version "7.13.14"
+  resolved "https://registry.yarnpkg.com/@babel/register/-/register-7.13.14.tgz#bbfa8f4f027c2ebc432e8e69e078b632605f2d9b"
+  integrity sha512-iyw0hUwjh/fzN8qklVqZodbyWjEBOG0KdDnBOpv3zzIgK3NmuRXBmIXH39ZBdspkn8LTHvSboN+oYb4MT43+9Q==
   dependencies:
     find-cache-dir "^2.0.0"
-    lodash "^4.17.13"
+    lodash "^4.17.19"
     make-dir "^2.1.0"
     pirates "^4.0.0"
     source-map-support "^0.5.16"
 
-"@babel/runtime-corejs3@^7.8.7":
-  version "7.8.7"
-  resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.8.7.tgz#8209d9dff2f33aa2616cb319c83fe159ffb07b8c"
-  integrity sha512-sc7A+H4I8kTd7S61dgB9RomXu/C+F4IrRr4Ytze4dnfx7AXEpCrejSNpjx7vq6y/Bak9S6Kbk65a/WgMLtg43Q==
+"@babel/runtime-corejs3@^7.13.10":
+  version "7.13.10"
+  resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.13.10.tgz#14c3f4c85de22ba88e8e86685d13e8861a82fe86"
+  integrity sha512-x/XYVQ1h684pp1mJwOV4CyvqZXqbc8CMsMGUnAbuc82ZCdv1U63w5RSUzgDSXQHG5Rps/kiksH6g2D5BuaKyXg==
   dependencies:
     core-js-pure "^3.0.0"
     regenerator-runtime "^0.13.4"
@@ -830,37 +872,36 @@
   dependencies:
     regenerator-runtime "^0.13.4"
 
-"@babel/template@^7.10.1", "@babel/template@^7.7.4":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.10.1.tgz#e167154a94cb5f14b28dc58f5356d2162f539811"
-  integrity sha512-OQDg6SqvFSsc9A0ej6SKINWrpJiNonRIniYondK2ViKhB06i3c0s+76XUft71iqBEe9S1OKsHwPAjfHnuvnCig==
+"@babel/template@^7.12.13", "@babel/template@^7.7.4":
+  version "7.12.13"
+  resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.12.13.tgz#530265be8a2589dbb37523844c5bcb55947fb327"
+  integrity sha512-/7xxiGA57xMo/P2GVvdEumr8ONhFOhfgq2ihK3h1e6THqzTAkHbkXgB0xI9yeTfIUoH3+oAeHhqm/I43OTbbjA==
   dependencies:
-    "@babel/code-frame" "^7.10.1"
-    "@babel/parser" "^7.10.1"
-    "@babel/types" "^7.10.1"
+    "@babel/code-frame" "^7.12.13"
+    "@babel/parser" "^7.12.13"
+    "@babel/types" "^7.12.13"
 
-"@babel/traverse@^7.10.1", "@babel/traverse@^7.7.4":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.10.1.tgz#bbcef3031e4152a6c0b50147f4958df54ca0dd27"
-  integrity sha512-C/cTuXeKt85K+p08jN6vMDz8vSV0vZcI0wmQ36o6mjbuo++kPMdpOYw23W2XH04dbRt9/nMEfA4W3eR21CD+TQ==
+"@babel/traverse@^7.13.0", "@babel/traverse@^7.13.13", "@babel/traverse@^7.7.4":
+  version "7.13.13"
+  resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.13.13.tgz#39aa9c21aab69f74d948a486dd28a2dbdbf5114d"
+  integrity sha512-CblEcwmXKR6eP43oQGG++0QMTtCjAsa3frUuzHoiIJWpaIIi8dwMyEFUJoXRLxagGqCK+jALRwIO+o3R9p/uUg==
   dependencies:
-    "@babel/code-frame" "^7.10.1"
-    "@babel/generator" "^7.10.1"
-    "@babel/helper-function-name" "^7.10.1"
-    "@babel/helper-split-export-declaration" "^7.10.1"
-    "@babel/parser" "^7.10.1"
-    "@babel/types" "^7.10.1"
+    "@babel/code-frame" "^7.12.13"
+    "@babel/generator" "^7.13.9"
+    "@babel/helper-function-name" "^7.12.13"
+    "@babel/helper-split-export-declaration" "^7.12.13"
+    "@babel/parser" "^7.13.13"
+    "@babel/types" "^7.13.13"
     debug "^4.1.0"
     globals "^11.1.0"
-    lodash "^4.17.13"
 
-"@babel/types@^7.10.1", "@babel/types@^7.4.4":
-  version "7.10.1"
-  resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.10.1.tgz#6886724d31c8022160a7db895e6731ca33483921"
-  integrity sha512-L2yqUOpf3tzlW9GVuipgLEcZxnO+96SzR6fjXMuxxNkIgFJ5+07mHCZ+HkHqaeZu8+3LKnNJJ1bKbjBETQAsrA==
+"@babel/types@^7.12.1", "@babel/types@^7.12.13", "@babel/types@^7.13.0", "@babel/types@^7.13.12", "@babel/types@^7.13.13", "@babel/types@^7.13.14", "@babel/types@^7.4.4":
+  version "7.13.14"
+  resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.13.14.tgz#c35a4abb15c7cd45a2746d78ab328e362cbace0d"
+  integrity sha512-A2aa3QTkWoyqsZZFl56MLUsfmh7O0gN41IPvXAE/++8ojpbz12SszD7JEGYVdn4f9Kt4amIei07swF1h4AqmmQ==
   dependencies:
-    "@babel/helper-validator-identifier" "^7.10.1"
-    lodash "^4.17.13"
+    "@babel/helper-validator-identifier" "^7.12.11"
+    lodash "^4.17.19"
     to-fast-properties "^2.0.0"
 
 "@evocateur/libnpmaccess@^3.1.2":
@@ -1645,11 +1686,49 @@
     call-me-maybe "^1.0.1"
     glob-to-regexp "^0.3.0"
 
+"@nicolo-ribaudo/chokidar-2@2.1.8-no-fsevents":
+  version "2.1.8-no-fsevents"
+  resolved "https://registry.yarnpkg.com/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.tgz#da7c3996b8e6e19ebd14d82eaced2313e7769f9b"
+  integrity sha512-+nb9vWloHNNMFHjGofEam3wopE3m1yuambrrd/fnPc+lFOMB9ROTqQlche9ByFWNkdNqfSgR/kkQtQ8DzEWt2w==
+  dependencies:
+    anymatch "^2.0.0"
+    async-each "^1.0.1"
+    braces "^2.3.2"
+    glob-parent "^3.1.0"
+    inherits "^2.0.3"
+    is-binary-path "^1.0.0"
+    is-glob "^4.0.0"
+    normalize-path "^3.0.0"
+    path-is-absolute "^1.0.0"
+    readdirp "^2.2.1"
+    upath "^1.1.1"
+
+"@nodelib/fs.scandir@2.1.3":
+  version "2.1.3"
+  resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz#3a582bdb53804c6ba6d146579c46e52130cf4a3b"
+  integrity sha512-eGmwYQn3gxo4r7jdQnkrrN6bY478C3P+a/y72IJukF8LjB6ZHeB3c+Ehacj3sYeSmUXGlnA67/PmbM9CVwL7Dw==
+  dependencies:
+    "@nodelib/fs.stat" "2.0.3"
+    run-parallel "^1.1.9"
+
+"@nodelib/fs.stat@2.0.3", "@nodelib/fs.stat@^2.0.2":
+  version "2.0.3"
+  resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz#34dc5f4cabbc720f4e60f75a747e7ecd6c175bd3"
+  integrity sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA==
+
 "@nodelib/fs.stat@^1.1.2":
   version "1.1.3"
   resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz#2b5a3ab3f918cca48a8c754c08168e3f03eba61b"
   integrity sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==
 
+"@nodelib/fs.walk@^1.2.3":
+  version "1.2.4"
+  resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.4.tgz#011b9202a70a6366e436ca5c065844528ab04976"
+  integrity sha512-1V9XOY4rDW0rehzbrcqAmHnz8e7SKvX27gh8Gt2WgB0+pdzdiLV83p72kZPU+jvMbS1qU5mauP2iOvO8rhmurQ==
+  dependencies:
+    "@nodelib/fs.scandir" "2.1.3"
+    fastq "^1.6.0"
+
 "@octokit/endpoint@^5.1.0":
   version "5.3.6"
   resolved "https://registry.yarnpkg.com/@octokit/endpoint/-/endpoint-5.3.6.tgz#58a67b75b853127568e0db533cdd10f3bdca2e23"
@@ -1719,11 +1798,6 @@
   resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0"
   integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==
 
-"@types/eslint-visitor-keys@^1.0.0":
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d"
-  integrity sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag==
-
 "@types/events@*":
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7"
@@ -1738,11 +1812,16 @@
     "@types/minimatch" "*"
     "@types/node" "*"
 
-"@types/json-schema@^7.0.3", "@types/json-schema@^7.0.4":
+"@types/json-schema@^7.0.3":
   version "7.0.5"
   resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.5.tgz#dcce4430e64b443ba8945f0290fb564ad5bac6dd"
   integrity sha512-7+2BITlgjgDhH0vvwZU/HZJVyk+2XUlvxXe8dFMedNX/aMkaOq++rMAFXc0tM7ij15QaWlbdQASBR9dihi+bDQ==
 
+"@types/json-schema@^7.0.6":
+  version "7.0.7"
+  resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.7.tgz#98a993516c859eb0d5c4c8f098317a9ea68db9ad"
+  integrity sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==
+
 "@types/json5@^0.0.29":
   version "0.0.29"
   resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
@@ -1783,65 +1862,75 @@
   dependencies:
     "@types/node" "*"
 
-"@typescript-eslint/eslint-plugin@^3.7.0":
-  version "3.7.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-3.7.0.tgz#0f91aa3c83d019591719e597fbdb73a59595a263"
-  integrity sha512-4OEcPON3QIx0ntsuiuFP/TkldmBGXf0uKxPQlGtS/W2F3ndYm8Vgdpj/woPJkzUc65gd3iR+qi3K8SDQP/obFg==
+"@typescript-eslint/eslint-plugin@^4.20.0":
+  version "4.20.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.20.0.tgz#9d8794bd99aad9153092ad13c96164e3082e9a92"
+  integrity sha512-sw+3HO5aehYqn5w177z2D82ZQlqHCwcKSMboueo7oE4KU9QiC0SAgfS/D4z9xXvpTc8Bt41Raa9fBR8T2tIhoQ==
   dependencies:
-    "@typescript-eslint/experimental-utils" "3.7.0"
+    "@typescript-eslint/experimental-utils" "4.20.0"
+    "@typescript-eslint/scope-manager" "4.20.0"
     debug "^4.1.1"
     functional-red-black-tree "^1.0.1"
+    lodash "^4.17.15"
     regexpp "^3.0.0"
     semver "^7.3.2"
     tsutils "^3.17.1"
 
-"@typescript-eslint/experimental-utils@3.7.0":
-  version "3.7.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-3.7.0.tgz#0ee21f6c48b2b30c63211da23827725078d5169a"
-  integrity sha512-xpfXXAfZqhhqs5RPQBfAFrWDHoNxD5+sVB5A46TF58Bq1hRfVROrWHcQHHUM9aCBdy9+cwATcvCbRg8aIRbaHQ==
+"@typescript-eslint/experimental-utils@4.20.0":
+  version "4.20.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.20.0.tgz#a8ab2d7b61924f99042b7d77372996d5f41dc44b"
+  integrity sha512-sQNlf6rjLq2yB5lELl3gOE7OuoA/6IVXJUJ+Vs7emrQMva14CkOwyQwD7CW+TkmOJ4Q/YGmoDLmbfFrpGmbKng==
   dependencies:
     "@types/json-schema" "^7.0.3"
-    "@typescript-eslint/types" "3.7.0"
-    "@typescript-eslint/typescript-estree" "3.7.0"
+    "@typescript-eslint/scope-manager" "4.20.0"
+    "@typescript-eslint/types" "4.20.0"
+    "@typescript-eslint/typescript-estree" "4.20.0"
     eslint-scope "^5.0.0"
     eslint-utils "^2.0.0"
 
-"@typescript-eslint/parser@^3.7.0":
-  version "3.7.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-3.7.0.tgz#3e9cd9df9ea644536feb6e5acdb8279ecff96ce9"
-  integrity sha512-2LZauVUt7jAWkcIW7djUc3kyW+fSarNEuM3RF2JdLHR9BfX/nDEnyA4/uWz0wseoWVZbDXDF7iF9Jc342flNqQ==
+"@typescript-eslint/parser@^4.20.0":
+  version "4.20.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.20.0.tgz#8dd403c8b4258b99194972d9799e201b8d083bdd"
+  integrity sha512-m6vDtgL9EABdjMtKVw5rr6DdeMCH3OA1vFb0dAyuZSa3e5yw1YRzlwFnm9knma9Lz6b2GPvoNSa8vOXrqsaglA==
   dependencies:
-    "@types/eslint-visitor-keys" "^1.0.0"
-    "@typescript-eslint/experimental-utils" "3.7.0"
-    "@typescript-eslint/types" "3.7.0"
-    "@typescript-eslint/typescript-estree" "3.7.0"
-    eslint-visitor-keys "^1.1.0"
-
-"@typescript-eslint/types@3.7.0":
-  version "3.7.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-3.7.0.tgz#09897fab0cb95479c01166b10b2c03c224821077"
-  integrity sha512-reCaK+hyKkKF+itoylAnLzFeNYAEktB0XVfSQvf0gcVgpz1l49Lt6Vo9x4MVCCxiDydA0iLAjTF/ODH0pbfnpg==
-
-"@typescript-eslint/typescript-estree@3.7.0":
-  version "3.7.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-3.7.0.tgz#66872e6da120caa4b64e6b4ca5c8702afc74738d"
-  integrity sha512-xr5oobkYRebejlACGr1TJ0Z/r0a2/HUf0SXqPvlgUMwiMqOCu/J+/Dr9U3T0IxpE5oLFSkqMx1FE/dKaZ8KsOQ==
-  dependencies:
-    "@typescript-eslint/types" "3.7.0"
-    "@typescript-eslint/visitor-keys" "3.7.0"
+    "@typescript-eslint/scope-manager" "4.20.0"
+    "@typescript-eslint/types" "4.20.0"
+    "@typescript-eslint/typescript-estree" "4.20.0"
     debug "^4.1.1"
-    glob "^7.1.6"
+
+"@typescript-eslint/scope-manager@4.20.0":
+  version "4.20.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.20.0.tgz#953ecbf3b00845ece7be66246608be9d126d05ca"
+  integrity sha512-/zm6WR6iclD5HhGpcwl/GOYDTzrTHmvf8LLLkwKqqPKG6+KZt/CfSgPCiybshmck66M2L5fWSF/MKNuCwtKQSQ==
+  dependencies:
+    "@typescript-eslint/types" "4.20.0"
+    "@typescript-eslint/visitor-keys" "4.20.0"
+
+"@typescript-eslint/types@4.20.0":
+  version "4.20.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.20.0.tgz#c6cf5ef3c9b1c8f699a9bbdafb7a1da1ca781225"
+  integrity sha512-cYY+1PIjei1nk49JAPnH1VEnu7OYdWRdJhYI5wiKOUMhLTG1qsx5cQxCUTuwWCmQoyriadz3Ni8HZmGSofeC+w==
+
+"@typescript-eslint/typescript-estree@4.20.0":
+  version "4.20.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.20.0.tgz#8b3b08f85f18a8da5d88f65cb400f013e88ab7be"
+  integrity sha512-Knpp0reOd4ZsyoEJdW8i/sK3mtZ47Ls7ZHvD8WVABNx5Xnn7KhenMTRGegoyMTx6TiXlOVgMz9r0pDgXTEEIHA==
+  dependencies:
+    "@typescript-eslint/types" "4.20.0"
+    "@typescript-eslint/visitor-keys" "4.20.0"
+    debug "^4.1.1"
+    globby "^11.0.1"
     is-glob "^4.0.1"
-    lodash "^4.17.15"
     semver "^7.3.2"
     tsutils "^3.17.1"
 
-"@typescript-eslint/visitor-keys@3.7.0":
-  version "3.7.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-3.7.0.tgz#ac0417d382a136e4571a0b0dcfe52088cb628177"
-  integrity sha512-k5PiZdB4vklUpUX4NBncn5RBKty8G3ihTY+hqJsCdMuD0v4jofI5xuqwnVcWxfv6iTm2P/dfEa2wMUnsUY8ODw==
+"@typescript-eslint/visitor-keys@4.20.0":
+  version "4.20.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.20.0.tgz#1e84db034da13f208325e6bfc995c3b75f7dbd62"
+  integrity sha512-NXKRM3oOVQL8yNFDNCZuieRIwZ5UtjNLYtmMx2PacEAGmbaEYtGgVHUHVyZvU/0rYZcizdrWjDo+WBtRPSgq+A==
   dependencies:
-    eslint-visitor-keys "^1.1.0"
+    "@typescript-eslint/types" "4.20.0"
+    eslint-visitor-keys "^2.0.0"
 
 "@webassemblyjs/ast@1.8.5":
   version "1.8.5"
@@ -2101,7 +2190,12 @@
   resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.4.1.tgz#ef916e271c64ac12171fd8384eaae6b2345854da"
   integrity sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ==
 
-ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.11.0, ajv@^6.12.2, ajv@^6.5.5:
+ajv-keywords@^3.5.2:
+  version "3.5.2"
+  resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d"
+  integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==
+
+ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.11.0, ajv@^6.5.5:
   version "6.12.3"
   resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.3.tgz#18c5af38a111ddeb4f2697bd78d68abc1cabd706"
   integrity sha512-4K0cK3L1hsqk9xIb2z9vs/XU+PGJZ9PNpJRDS9YLzmNdX6jmVPfamLvTJr0aDAusnHyCHO6MjzlkAsgtqp9teA==
@@ -2111,6 +2205,16 @@
     json-schema-traverse "^0.4.1"
     uri-js "^4.2.2"
 
+ajv@^6.12.5:
+  version "6.12.6"
+  resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
+  integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==
+  dependencies:
+    fast-deep-equal "^3.1.1"
+    fast-json-stable-stringify "^2.0.0"
+    json-schema-traverse "^0.4.1"
+    uri-js "^4.2.2"
+
 ansi-colors@4.1.1, ansi-colors@^4.1.1:
   version "4.1.1"
   resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348"
@@ -2290,6 +2394,11 @@
   dependencies:
     array-uniq "^1.0.1"
 
+array-union@^2.1.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d"
+  integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==
+
 array-uniq@^1.0.1:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6"
@@ -2372,11 +2481,6 @@
   resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9"
   integrity sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==
 
-async-array-reduce@^0.2.0:
-  version "0.2.1"
-  resolved "https://registry.yarnpkg.com/async-array-reduce/-/async-array-reduce-0.2.1.tgz#c8be010a2b5cd00dea96c81116034693dfdd82d1"
-  integrity sha1-yL4BCitc0A3qlsgRFgNGk9/dgtE=
-
 async-each@^1.0.1:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf"
@@ -2399,6 +2503,11 @@
   resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
   integrity sha1-x57Zf380y48robyXkLzDZkdLS3k=
 
+at-least-node@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2"
+  integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==
+
 atob-lite@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/atob-lite/-/atob-lite-2.0.0.tgz#0fef5ad46f1bd7a8502c65727f0367d5ee43d696"
@@ -2465,6 +2574,30 @@
     reselect "^4.0.0"
     resolve "^1.13.1"
 
+babel-plugin-polyfill-corejs2@^0.1.4:
+  version "0.1.10"
+  resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.1.10.tgz#a2c5c245f56c0cac3dbddbf0726a46b24f0f81d1"
+  integrity sha512-DO95wD4g0A8KRaHKi0D51NdGXzvpqVLnLu5BTvDlpqUEpTmeEtypgC1xqesORaWmiUOQI14UHKlzNd9iZ2G3ZA==
+  dependencies:
+    "@babel/compat-data" "^7.13.0"
+    "@babel/helper-define-polyfill-provider" "^0.1.5"
+    semver "^6.1.1"
+
+babel-plugin-polyfill-corejs3@^0.1.3:
+  version "0.1.7"
+  resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.1.7.tgz#80449d9d6f2274912e05d9e182b54816904befd0"
+  integrity sha512-u+gbS9bbPhZWEeyy1oR/YaaSpod/KDT07arZHb80aTpl8H5ZBq+uN1nN9/xtX7jQyfLdPfoqI4Rue/MQSWJquw==
+  dependencies:
+    "@babel/helper-define-polyfill-provider" "^0.1.5"
+    core-js-compat "^3.8.1"
+
+babel-plugin-polyfill-regenerator@^0.1.2:
+  version "0.1.6"
+  resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.1.6.tgz#0fe06a026fe0faa628ccc8ba3302da0a6ce02f3f"
+  integrity sha512-OUrYG9iKPKz8NxswXbRAdSwF0GhRdIEMTloQATJi4bDuFqrXaXcCUT/VGNrr8pBcjMh1RxZ7Xt9cytVJTJfvMg==
+  dependencies:
+    "@babel/helper-define-polyfill-provider" "^0.1.5"
+
 babel-plugin-preserve-comment-header@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/babel-plugin-preserve-comment-header/-/babel-plugin-preserve-comment-header-1.0.1.tgz#7181cba65c507914d45eaefc372eb659b3f16996"
@@ -2675,15 +2808,16 @@
   dependencies:
     pako "~1.0.5"
 
-browserslist@^4.12.0, browserslist@^4.8.3:
-  version "4.12.0"
-  resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.12.0.tgz#06c6d5715a1ede6c51fc39ff67fd647f740b656d"
-  integrity sha512-UH2GkcEDSI0k/lRkuDSzFl9ZZ87skSy9w2XAn1MsZnL+4c4rqbBd3e82UWHbYDpztABrPBhZsTEeuxVfHppqDg==
+browserslist@^4.14.5, browserslist@^4.16.3:
+  version "4.16.3"
+  resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.16.3.tgz#340aa46940d7db878748567c5dea24a48ddf3717"
+  integrity sha512-vIyhWmIkULaq04Gt93txdh+j02yX/JzlyhLYbV3YQCn/zvES3JnY7TifHHvvr1w5hTDluNKMkV05cs4vy8Q7sw==
   dependencies:
-    caniuse-lite "^1.0.30001043"
-    electron-to-chromium "^1.3.413"
-    node-releases "^1.1.53"
-    pkg-up "^2.0.0"
+    caniuse-lite "^1.0.30001181"
+    colorette "^1.2.1"
+    electron-to-chromium "^1.3.649"
+    escalade "^3.1.1"
+    node-releases "^1.1.70"
 
 btoa-lite@^1.0.0:
   version "1.0.0"
@@ -2851,22 +2985,10 @@
   resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320"
   integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
 
-camelcase@^6.0.0:
-  version "6.0.0"
-  resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.0.0.tgz#5259f7c30e35e278f1bdc2a4d91230b37cad981e"
-  integrity sha512-8KMDF1Vz2gzOq54ONPJS65IvTUaB1cHJ2DMM7MbPmLZljDH1qpzzLsWdiN9pHh6qvkRVDTi/07+eNGch/oLU4w==
-
-caniuse-lite@^1.0.30001043:
-  version "1.0.30001066"
-  resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001066.tgz#0a8a58a10108f2b9bf38e7b65c237b12fd9c5f04"
-  integrity sha512-Gfj/WAastBtfxLws0RCh2sDbTK/8rJuSeZMecrSkNGYxPcv7EzblmDGfWQCFEQcSqYE2BRgQiJh8HOD07N5hIw==
-
-cartesian@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/cartesian/-/cartesian-1.0.1.tgz#ae3fc8a63e2ba7e2c4989ce696207457bcae65af"
-  integrity sha1-rj/Ipj4rp+LEmJzmliB0V7yuZa8=
-  dependencies:
-    xtend "^4.0.1"
+caniuse-lite@^1.0.30001181:
+  version "1.0.30001205"
+  resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001205.tgz#d79bf6a6fb13196b4bb46e5143a22ca0242e0ef8"
+  integrity sha512-TL1GrS5V6LElbitPazidkBMD9sa448bQDDLrumDqaggmKFcuU2JW1wTOHJPukAcOMtEmLcmDJEzfRrf+GjM0Og==
 
 caseless@~0.12.0:
   version "0.12.0"
@@ -2965,6 +3087,21 @@
   optionalDependencies:
     fsevents "^1.2.7"
 
+chokidar@^3.4.0:
+  version "3.5.1"
+  resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.1.tgz#ee9ce7bbebd2b79f49f304799d5468e31e14e68a"
+  integrity sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==
+  dependencies:
+    anymatch "~3.1.1"
+    braces "~3.0.2"
+    glob-parent "~5.1.0"
+    is-binary-path "~2.1.0"
+    is-glob "~4.0.1"
+    normalize-path "~3.0.0"
+    readdirp "~3.5.0"
+  optionalDependencies:
+    fsevents "~2.3.1"
+
 chownr@^1.1.1, chownr@^1.1.2:
   version "1.1.4"
   resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b"
@@ -3103,6 +3240,16 @@
   resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
   integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
 
+colorette@^1.2.1:
+  version "1.2.1"
+  resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.1.tgz#4d0b921325c14faf92633086a536db6e89564b1b"
+  integrity sha512-puCDz0CzydiSYOrnXpz/PKd69zRrribezjtE9yd4zvytoRc8+RY/KJPvtPFKZS3E3wP6neGyMe0vOTlHO5L3Pw==
+
+colors@^1.4.0:
+  version "1.4.0"
+  resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78"
+  integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==
+
 columnify@^1.5.4:
   version "1.5.4"
   resolved "https://registry.yarnpkg.com/columnify/-/columnify-1.5.4.tgz#4737ddf1c7b69a8a7c340570782e947eec8e78bb"
@@ -3196,6 +3343,21 @@
     readable-stream "^3.0.2"
     typedarray "^0.0.6"
 
+concurrently@^5.3.0:
+  version "5.3.0"
+  resolved "https://registry.yarnpkg.com/concurrently/-/concurrently-5.3.0.tgz#7500de6410d043c912b2da27de3202cb489b1e7b"
+  integrity sha512-8MhqOB6PWlBfA2vJ8a0bSFKATOdWlHiQlk11IfmQBPaHVP8oP2gsh2MObE6UR3hqDHqvaIvLTyceNW6obVuFHQ==
+  dependencies:
+    chalk "^2.4.2"
+    date-fns "^2.0.1"
+    lodash "^4.17.15"
+    read-pkg "^4.0.1"
+    rxjs "^6.5.2"
+    spawn-command "^0.0.2-1"
+    supports-color "^6.1.0"
+    tree-kill "^1.2.2"
+    yargs "^13.3.0"
+
 config-chain@^1.1.11:
   version "1.1.12"
   resolved "https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.12.tgz#0fde8d091200eb5e808caf25fe618c02f48e4efa"
@@ -3360,12 +3522,12 @@
   resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d"
   integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=
 
-core-js-compat@^3.6.2:
-  version "3.6.4"
-  resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.6.4.tgz#938476569ebb6cda80d339bcf199fae4f16fff17"
-  integrity sha512-zAa3IZPvsJ0slViBQ2z+vgyyTuhd3MFn1rBQjZSKVEgB0UMYhUkCj9jJUVPgGTGqWvsBVmfnruXgTcNyTlEiSA==
+core-js-compat@^3.8.1, core-js-compat@^3.9.0:
+  version "3.10.0"
+  resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.10.0.tgz#3600dc72869673c110215ee7a005a8609dea0fe1"
+  integrity sha512-9yVewub2MXNYyGvuLnMHcN1k9RkvB7/ofktpeKTIaASyB88YYqGzUnu0ywMMhJrDHOMiTjSHWGzR+i7Wb9Z1kQ==
   dependencies:
-    browserslist "^4.8.3"
+    browserslist "^4.16.3"
     semver "7.0.0"
 
 core-js-pure@^3.0.0:
@@ -3373,11 +3535,6 @@
   resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.3.4.tgz#01d2842f552a866265dc77ededb2ccd668ff2879"
   integrity sha512-hqxt6XpR4zIMNUY920oNyAtwaq4yg8IScmXumnfyRWF9+ur7wtjr/4eCdfTJzY64jmi8WRCwIqNBKzYeOKdvnw==
 
-core-js@^3.6.4:
-  version "3.6.4"
-  resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.6.4.tgz#440a83536b458114b9cb2ac1580ba377dc470647"
-  integrity sha512-4paDGScNgZP2IXXilaffL9X7968RuvwlkK3xWtZRVqgd8SYNiVKRJvkFd1aqqEuPfN7E68ZHEp9hDj6lHj4Hyw==
-
 core-util-is@1.0.2, core-util-is@~1.0.0:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
@@ -3479,30 +3636,6 @@
     randombytes "^2.0.0"
     randomfill "^1.0.3"
 
-css-loader@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-4.0.0.tgz#814434d4e1e2d5f430c70e85e78268db7f3cced1"
-  integrity sha512-/7d5slKnmY2S39FNifJ7JQ8MhcMM/rDIjAZ2Sc/Z8lnOWOmc10hijg28ovBtljY364pQaF01O2nj5AIBDnJ9vQ==
-  dependencies:
-    camelcase "^6.0.0"
-    cssesc "^3.0.0"
-    icss-utils "^4.1.1"
-    loader-utils "^2.0.0"
-    normalize-path "^3.0.0"
-    postcss "^7.0.32"
-    postcss-modules-extract-imports "^2.0.0"
-    postcss-modules-local-by-default "^3.0.3"
-    postcss-modules-scope "^2.2.0"
-    postcss-modules-values "^3.0.0"
-    postcss-value-parser "^4.1.0"
-    schema-utils "^2.7.0"
-    semver "^7.3.2"
-
-cssesc@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee"
-  integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==
-
 cssom@^0.4.4:
   version "0.4.4"
   resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.4.4.tgz#5a66cf93d2d0b661d80bf6a44fb65f5c2e4e0a10"
@@ -3560,6 +3693,11 @@
   resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-1.30.1.tgz#2e71bf0b119153dbb4cc4e88d9ea5acfb50dc05c"
   integrity sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw==
 
+date-fns@^2.0.1:
+  version "2.16.1"
+  resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.16.1.tgz#05775792c3f3331da812af253e1a935851d3834b"
+  integrity sha512-sAJVKx/FqrLYHAQeN7VpJrPhagZc9R4ImZIWYRFZaaohR3KzmuK88touwsSwSVT8Qcbd4zoDsnGfX4GFB4imyQ==
+
 date-now@^0.1.4:
   version "0.1.4"
   resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b"
@@ -3796,6 +3934,13 @@
   dependencies:
     path-type "^3.0.0"
 
+dir-glob@^3.0.1:
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f"
+  integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==
+  dependencies:
+    path-type "^4.0.0"
+
 dns-equal@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/dns-equal/-/dns-equal-1.0.0.tgz#b39e7f1da6eb0a75ba9c17324b34753c47e0654d"
@@ -3831,11 +3976,6 @@
   dependencies:
     esutils "^2.0.2"
 
-dom-seek@^5.1.0:
-  version "5.1.1"
-  resolved "https://registry.yarnpkg.com/dom-seek/-/dom-seek-5.1.1.tgz#4e35bee763b6ba082f372345823ec9665d1fbf26"
-  integrity sha512-1strSwd201Gfhfkfsk77SX9xyJGzu12gqUo5Q0W3Njtj2QxcfQTwCDOynZ6npZ4ASUFRQq0asjYDRlFxYPKwTA==
-
 domain-browser@^1.1.1:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda"
@@ -3890,10 +4030,10 @@
   resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
   integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=
 
-electron-to-chromium@^1.3.413:
-  version "1.3.453"
-  resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.453.tgz#758a8565a64b7889b27132a51d2abb8b135c9d01"
-  integrity sha512-IQbCfjJR0NDDn/+vojTlq7fPSREcALtF8M1n01gw7nQghCtfFYrJ2dfhsp8APr8bANoFC8vRTFVXMOGpT0eetw==
+electron-to-chromium@^1.3.649:
+  version "1.3.707"
+  resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.707.tgz#71386d0ceca6727835c33ba31f507f6824d18c35"
+  integrity sha512-BqddgxNPrcWnbDdJw7SzXVzPmp+oiyjVrc7tkQVaznPGSS9SKZatw6qxoP857M+HbOyyqJQwYQtsuFIMSTNSZA==
 
 elegant-spinner@^1.0.1:
   version "1.0.1"
@@ -3931,6 +4071,7 @@
 emojis-list@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78"
+  integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==
 
 encodeurl@~1.0.2:
   version "1.0.2"
@@ -4057,6 +4198,11 @@
   dependencies:
     es6-promise "^4.0.3"
 
+escalade@^3.1.1:
+  version "3.1.1"
+  resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40"
+  integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==
+
 escape-html@~1.0.3:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
@@ -4184,6 +4330,11 @@
   resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e"
   integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==
 
+eslint-visitor-keys@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz#21fdc8fbcd9c795cc0321f0563702095751511a8"
+  integrity sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ==
+
 eslint@^7.5.0:
   version "7.5.0"
   resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.5.0.tgz#9ecbfad62216d223b82ac9ffea7ef3444671d135"
@@ -4346,13 +4497,6 @@
     snapdragon "^0.8.1"
     to-regex "^3.0.1"
 
-expand-tilde@^1.2.2:
-  version "1.2.2"
-  resolved "https://registry.yarnpkg.com/expand-tilde/-/expand-tilde-1.2.2.tgz#0b81eba897e5a3d31d1c3d102f8f01441e559449"
-  integrity sha1-C4HrqJflo9MdHD0QL48BRB5VlEk=
-  dependencies:
-    os-homedir "^1.0.1"
-
 expand-tilde@^2.0.0, expand-tilde@^2.0.2:
   version "2.0.2"
   resolved "https://registry.yarnpkg.com/expand-tilde/-/expand-tilde-2.0.2.tgz#97e801aa052df02454de46b02bf621642cdc8502"
@@ -4466,6 +4610,18 @@
     merge2 "^1.2.3"
     micromatch "^3.1.10"
 
+fast-glob@^3.1.1:
+  version "3.2.4"
+  resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.4.tgz#d20aefbf99579383e7f3cc66529158c9b98554d3"
+  integrity sha512-kr/Oo6PX51265qeuCYsyGypiO5uJFgBS0jksyG7FUeCyQzNwYnzrNIMR1NXfkZXsMYXYLRAHgISHBz8gQcxKHQ==
+  dependencies:
+    "@nodelib/fs.stat" "^2.0.2"
+    "@nodelib/fs.walk" "^1.2.3"
+    glob-parent "^5.1.0"
+    merge2 "^1.3.0"
+    micromatch "^4.0.2"
+    picomatch "^2.2.1"
+
 fast-json-stable-stringify@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2"
@@ -4476,6 +4632,13 @@
   resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
   integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=
 
+fastq@^1.6.0:
+  version "1.8.0"
+  resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.8.0.tgz#550e1f9f59bbc65fe185cb6a9b4d95357107f481"
+  integrity sha512-SMIZoZdLh/fgofivvIkmknUXyPnvxRE3DhtZ5Me3Mrsk5gyPL42F0xr51TdRXskBxHfMp+07bcYzfsYEsSQA9Q==
+  dependencies:
+    reusify "^1.0.4"
+
 faye-websocket@^0.10.0:
   version "0.10.0"
   resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.10.0.tgz#4e492f8d04dfb6f89003507f6edbf2d501e7c6f4"
@@ -4518,11 +4681,12 @@
     flat-cache "^2.0.1"
 
 file-loader@^6.0.0:
-  version "6.0.0"
-  resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-6.0.0.tgz#97bbfaab7a2460c07bcbd72d3a6922407f67649f"
+  version "6.2.0"
+  resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-6.2.0.tgz#baef7cf8e1840df325e4390b4484879480eebe4d"
+  integrity sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw==
   dependencies:
     loader-utils "^2.0.0"
-    schema-utils "^2.6.5"
+    schema-utils "^3.0.0"
 
 file-uri-to-path@1.0.0:
   version "1.0.0"
@@ -4734,11 +4898,6 @@
   resolved "https://registry.yarnpkg.com/fromentries/-/fromentries-1.2.0.tgz#e6aa06f240d6267f913cea422075ef88b63e7897"
   integrity sha512-33X7H/wdfO99GdRLLgkjUrD4geAFdq/Uv0kl3HD4da6HDixd2GUg8Mw7dahLCV9r/EARkmtYBB6Tch4EEokFTQ==
 
-fs-exists-sync@^0.1.0:
-  version "0.1.0"
-  resolved "https://registry.yarnpkg.com/fs-exists-sync/-/fs-exists-sync-0.1.0.tgz#982d6893af918e72d08dec9e8673ff2b5a8d6add"
-  integrity sha1-mC1ok6+RjnLQjeyehnP/K1qNat0=
-
 fs-extra@^8.1.0:
   version "8.1.0"
   resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0"
@@ -4748,6 +4907,16 @@
     jsonfile "^4.0.0"
     universalify "^0.1.0"
 
+fs-extra@^9.1.0:
+  version "9.1.0"
+  resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d"
+  integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==
+  dependencies:
+    at-least-node "^1.0.0"
+    graceful-fs "^4.2.0"
+    jsonfile "^6.0.1"
+    universalify "^2.0.0"
+
 fs-minipass@^1.2.5:
   version "1.2.7"
   resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.7.tgz#ccff8570841e7fe4265693da88936c55aed7f7c7"
@@ -4788,6 +4957,11 @@
   resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.3.tgz#fb738703ae8d2f9fe900c33836ddebee8b97f23e"
   integrity sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==
 
+fsevents@~2.3.1:
+  version "2.3.1"
+  resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.1.tgz#b209ab14c61012636c8863507edf7fb68cc54e9f"
+  integrity sha512-YR47Eg4hChJGAB1O3yEAOkGO+rlzutoICGqGo9EZ4lKWokzZRSyIW1QmTzqjtw8MJdj9srP869CuWw/hyzSiBw==
+
 function-bind@^1.1.1:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
@@ -4817,10 +4991,10 @@
   resolved "https://registry.yarnpkg.com/genfun/-/genfun-5.0.0.tgz#9dd9710a06900a5c4a5bf57aca5da4e52fe76537"
   integrity sha512-KGDOARWVga7+rnB3z9Sd2Letx515owfk0hSxHGuqjANb1M+x2bGZGqHLiozPsYMdM2OubeMni/Hpwmjq6qIUhA==
 
-gensync@^1.0.0-beta.1:
-  version "1.0.0-beta.1"
-  resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.1.tgz#58f4361ff987e5ff6e1e7a210827aa371eaac269"
-  integrity sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg==
+gensync@^1.0.0-beta.2:
+  version "1.0.0-beta.2"
+  resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0"
+  integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==
 
 get-caller-file@^1.0.1:
   version "1.0.3"
@@ -4943,7 +5117,7 @@
   dependencies:
     ini "^1.3.2"
 
-glob-parent@^3.0.1, glob-parent@^3.1.0:
+glob-parent@^3.1.0:
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae"
   integrity sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=
@@ -4951,10 +5125,10 @@
     is-glob "^3.1.0"
     path-dirname "^1.0.0"
 
-glob-parent@^5.0.0, glob-parent@~5.1.0:
-  version "5.1.0"
-  resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.0.tgz#5f4c1d1e748d30cd73ad2944b3577a81b081e8c2"
-  integrity sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw==
+glob-parent@^5.0.0, glob-parent@^5.1.0, glob-parent@~5.1.0:
+  version "5.1.1"
+  resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.1.tgz#b6c1ef417c4e5663ea498f1c45afac6916bbc229"
+  integrity sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==
   dependencies:
     is-glob "^4.0.1"
 
@@ -4963,7 +5137,7 @@
   resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab"
   integrity sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs=
 
-glob@7.1.6, glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.1, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6:
+glob@7.1.6, glob@^7.0.0, glob@^7.0.3, glob@^7.1.1, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6:
   version "7.1.6"
   resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6"
   integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==
@@ -4975,10 +5149,10 @@
     once "^1.3.0"
     path-is-absolute "^1.0.0"
 
-global-jsdom@^6.1.0:
-  version "6.1.0"
-  resolved "https://registry.yarnpkg.com/global-jsdom/-/global-jsdom-6.1.0.tgz#a911ec57c51cf72e93a2ce97925a02a6427aed76"
-  integrity sha512-zaNNWr7hpov5SgF21fVtvnliRcRMYSZGc47nSipUOw5Ktft+2njD4hjzN1OXWQzlBFnRU/W+MBN6OvaPMTawKQ==
+global-jsdom@^8.0.0:
+  version "8.0.0"
+  resolved "https://registry.yarnpkg.com/global-jsdom/-/global-jsdom-8.0.0.tgz#334501e24e24880c7687518826af0d38da6c2be5"
+  integrity sha512-fvOtNsQ4Ip1zkj7yZUzBhE/5pO8Iz6eb30niikooxjwQsQJS2XL6e8f0QlqfpJFBTfuM3dPK7XHUQmptjWJcuw==
 
 global-modules@2.0.0:
   version "2.0.0"
@@ -4987,14 +5161,6 @@
   dependencies:
     global-prefix "^3.0.0"
 
-global-modules@^0.2.3:
-  version "0.2.3"
-  resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-0.2.3.tgz#ea5a3bed42c6d6ce995a4f8a1269b5dae223828d"
-  integrity sha1-6lo77ULG1s6ZWk+KEmm12uIjgo0=
-  dependencies:
-    global-prefix "^0.1.4"
-    is-windows "^0.2.0"
-
 global-modules@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-1.0.0.tgz#6d770f0eb523ac78164d72b5e71a8877265cc3ea"
@@ -5004,16 +5170,6 @@
     is-windows "^1.0.1"
     resolve-dir "^1.0.0"
 
-global-prefix@^0.1.4:
-  version "0.1.5"
-  resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-0.1.5.tgz#8d3bc6b8da3ca8112a160d8d496ff0462bfef78f"
-  integrity sha1-jTvGuNo8qBEqFg2NSW/wRiv+948=
-  dependencies:
-    homedir-polyfill "^1.0.0"
-    ini "^1.3.4"
-    is-windows "^0.2.0"
-    which "^1.2.12"
-
 global-prefix@^1.0.1:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-1.0.2.tgz#dbf743c6c14992593c655568cb66ed32c0122ebe"
@@ -5046,6 +5202,18 @@
   dependencies:
     type-fest "^0.8.1"
 
+globby@^11.0.1:
+  version "11.0.1"
+  resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.1.tgz#9a2bf107a068f3ffeabc49ad702c79ede8cfd357"
+  integrity sha512-iH9RmgwCmUJHi2z5o2l3eTtGBtXek1OYlHrbcxOYugyHLmAsZrPj43OtHThd62Buh/Vv6VyCBD2bdyWcGNQqoQ==
+  dependencies:
+    array-union "^2.1.0"
+    dir-glob "^3.0.1"
+    fast-glob "^3.1.1"
+    ignore "^5.1.4"
+    merge2 "^1.3.0"
+    slash "^3.0.0"
+
 globby@^6.1.0:
   version "6.1.0"
   resolved "https://registry.yarnpkg.com/globby/-/globby-6.1.0.tgz#f5a6d70e8395e21c858fb0489d64df02424d506c"
@@ -5086,14 +5254,15 @@
   resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-2.0.0.tgz#0e039695ff50c93fc288557d696f3c1dc6776754"
   integrity sha512-d4sze1JNC454Wdo2fkuyzCr6aHcbL6PGGuFAz0Li/NcOm1tCHGnWDRmJP85dh9IhQErTc2svWFEX5xHIOo//kQ==
 
-handlebars@^4.1.2:
-  version "4.3.4"
-  resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.3.4.tgz#aab065294c27ad16ff4e711240a7288d2753306d"
-  integrity sha512-vvpo6mpK4ScNC1DbGRZ2d5BznS6ht0r1hi20RivsibMc6jNvFAeZQ6qk5VNspo6SOwVOJQbjHyBCpuS7BzA1pw==
+handlebars@^4.1.2, handlebars@^4.7.7:
+  version "4.7.7"
+  resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.7.tgz#9ce33416aad02dbd6c8fafa8240d5d98004945a1"
+  integrity sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==
   dependencies:
+    minimist "^1.2.5"
     neo-async "^2.6.0"
-    optimist "^0.6.1"
     source-map "^0.6.1"
+    wordwrap "^1.0.0"
   optionalDependencies:
     uglify-js "^3.1.4"
 
@@ -5127,13 +5296,6 @@
   resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
   integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
 
-has-glob@^0.1.1:
-  version "0.1.1"
-  resolved "https://registry.yarnpkg.com/has-glob/-/has-glob-0.1.1.tgz#a261c4c2a6c667e0c77b700a7f297c39ef3aa589"
-  integrity sha1-omHEwqbGZ+DHe3AKfyl8Oe86pYk=
-  dependencies:
-    is-glob "^2.0.1"
-
 has-symbols@^1.0.0, has-symbols@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8"
@@ -5220,7 +5382,7 @@
     minimalistic-assert "^1.0.0"
     minimalistic-crypto-utils "^1.0.1"
 
-homedir-polyfill@^1.0.0, homedir-polyfill@^1.0.1:
+homedir-polyfill@^1.0.1:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz#743298cef4e5af3e194161fbadcc2151d3a058e8"
   integrity sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==
@@ -5379,13 +5541,6 @@
   dependencies:
     safer-buffer ">= 2.1.2 < 3"
 
-icss-utils@^4.0.0, icss-utils@^4.1.1:
-  version "4.1.1"
-  resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-4.1.1.tgz#21170b53789ee27447c2f47dd683081403f9a467"
-  integrity sha512-4aFq7wvWyMHKgxsH8QQtGpvbASCf+eM3wPRLI6R+MgAnTCZ6STYsRvttLvRWK0Nfif5piF394St3HeJDaljGPA==
-  dependencies:
-    postcss "^7.0.14"
-
 ieee754@^1.1.4:
   version "1.1.13"
   resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84"
@@ -5408,10 +5563,10 @@
   resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc"
   integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==
 
-ignore@^5.1.1:
-  version "5.1.4"
-  resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.4.tgz#84b7b3dbe64552b6ef0eca99f6743dbec6d97adf"
-  integrity sha512-MzbUSahkTW1u7JpKKjY7LCARd1fU5W2rLdxlM4kdkayuCwZImjkpluF9CM1aLewYJguPDqewLam18Y6AU69A8A==
+ignore@^5.1.1, ignore@^5.1.4:
+  version "5.1.8"
+  resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57"
+  integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==
 
 import-fresh@^2.0.0:
   version "2.0.0"
@@ -5459,11 +5614,6 @@
   resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251"
   integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==
 
-indexes-of@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607"
-  integrity sha1-8w9xbI4r00bHtn0985FVZqfAVgc=
-
 infer-owner@^1.0.3, infer-owner@^1.0.4:
   version "1.0.4"
   resolved "https://registry.yarnpkg.com/infer-owner/-/infer-owner-1.0.4.tgz#c4cefcaa8e51051c2a40ba2ce8a3d27295af9467"
@@ -5533,18 +5683,11 @@
     default-gateway "^4.2.0"
     ipaddr.js "^1.9.0"
 
-interpret@1.2.0:
+interpret@1.2.0, interpret@^1.0.0:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.2.0.tgz#d5061a6224be58e8083985f5014d844359576296"
   integrity sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw==
 
-invariant@^2.2.2, invariant@^2.2.4:
-  version "2.2.4"
-  resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6"
-  integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==
-  dependencies:
-    loose-envify "^1.0.0"
-
 invert-kv@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-2.0.0.tgz#7393f5afa59ec9ff5f67a27620d11c226e3eec02"
@@ -5630,6 +5773,13 @@
   dependencies:
     ci-info "^2.0.0"
 
+is-core-module@^2.2.0:
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.2.0.tgz#97037ef3d52224d85163f5597b2b63d9afed981a"
+  integrity sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ==
+  dependencies:
+    has "^1.0.3"
+
 is-data-descriptor@^0.1.4:
   version "0.1.4"
   resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56"
@@ -5684,11 +5834,6 @@
   dependencies:
     is-plain-object "^2.0.4"
 
-is-extglob@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-1.0.0.tgz#ac468177c4943405a092fc8f29760c6ffc6206c0"
-  integrity sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=
-
 is-extglob@^2.1.0, is-extglob@^2.1.1:
   version "2.1.1"
   resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
@@ -5718,13 +5863,6 @@
   resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d"
   integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==
 
-is-glob@^2.0.1:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-2.0.1.tgz#d096f926a3ded5600f3fdfd91198cb0888c2d863"
-  integrity sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=
-  dependencies:
-    is-extglob "^1.0.0"
-
 is-glob@^3.1.0:
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a"
@@ -5879,16 +6017,6 @@
   resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72"
   integrity sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=
 
-is-valid-glob@^0.3.0:
-  version "0.3.0"
-  resolved "https://registry.yarnpkg.com/is-valid-glob/-/is-valid-glob-0.3.0.tgz#d4b55c69f51886f9b65c70d6c2622d37e29f48fe"
-  integrity sha1-1LVcafUYhvm2XHDWwmItN+KfSP4=
-
-is-windows@^0.2.0:
-  version "0.2.0"
-  resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-0.2.0.tgz#de1aa6d63ea29dd248737b69f1ff8b8002d2108c"
-  integrity sha1-3hqm1j6indJIc3tp8f+LgALSEIw=
-
 is-windows@^1.0.0, is-windows@^1.0.1, is-windows@^1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d"
@@ -6013,7 +6141,7 @@
     es-get-iterator "^1.0.2"
     iterate-iterator "^1.0.1"
 
-"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
+js-tokens@^4.0.0:
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
   integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
@@ -6129,6 +6257,15 @@
   optionalDependencies:
     graceful-fs "^4.1.6"
 
+jsonfile@^6.0.1:
+  version "6.1.0"
+  resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae"
+  integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==
+  dependencies:
+    universalify "^2.0.0"
+  optionalDependencies:
+    graceful-fs "^4.1.6"
+
 jsonparse@^1.2.0:
   version "1.3.1"
   resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280"
@@ -6173,13 +6310,6 @@
   resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd"
   integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==
 
-lazy-cache@^2.0.1:
-  version "2.0.2"
-  resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-2.0.2.tgz#b9190a4f913354694840859f8a8f7084d8822264"
-  integrity sha1-uRkKT5EzVGlIQIWfio9whNiCImQ=
-  dependencies:
-    set-getter "^0.1.0"
-
 lcid@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/lcid/-/lcid-2.0.0.tgz#6ef5d2df60e52f82eb228a4c373e8d1f397253cf"
@@ -6211,18 +6341,6 @@
     import-local "^2.0.0"
     npmlog "^4.1.2"
 
-leven@^3.1.0:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2"
-  integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==
-
-levenary@^1.1.1:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/levenary/-/levenary-1.1.1.tgz#842a9ee98d2075aa7faeedbe32679e9205f46f77"
-  integrity sha512-mkAdOIt79FD6irqjYSs4rdbnlT5vRonMEvBVPVb3XmevfS8kgRXwfes0dhPdEtzTWD/1eNE/Bm/G1iRt6DcnQQ==
-  dependencies:
-    leven "^3.1.0"
-
 levn@^0.4.1:
   version "0.4.1"
   resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade"
@@ -6354,7 +6472,7 @@
   resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.4.0.tgz#ed47066bfe534d7e84c4c7b9998c2a75607d9357"
   integrity sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw==
 
-loader-utils@1.2.3, loader-utils@^1.0.2, loader-utils@^1.1.0, loader-utils@^1.2.3:
+loader-utils@1.2.3, loader-utils@^1.0.2, loader-utils@^1.2.3:
   version "1.2.3"
   resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.2.3.tgz#1ff5dc6911c9f0a062531a4c04b609406108c2c7"
   integrity sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==
@@ -6366,6 +6484,7 @@
 loader-utils@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.0.tgz#e4cace5b816d425a166b5f097e10cd12b36064b0"
+  integrity sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==
   dependencies:
     big.js "^5.2.2"
     emojis-list "^3.0.0"
@@ -6404,6 +6523,11 @@
   resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef"
   integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=
 
+lodash.debounce@^4.0.8:
+  version "4.0.8"
+  resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
+  integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168=
+
 lodash.flattendeep@^4.4.0:
   version "4.4.0"
   resolved "https://registry.yarnpkg.com/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz#fb030917f86a3134e5bc9bec0d69e0013ddfedb2"
@@ -6449,10 +6573,10 @@
   resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
   integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=
 
-lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.2.1:
-  version "4.17.19"
-  resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b"
-  integrity sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==
+lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.21, lodash@^4.2.1:
+  version "4.17.21"
+  resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
+  integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
 
 log-symbols@3.0.0, log-symbols@^3.0.0:
   version "3.0.0"
@@ -6482,13 +6606,6 @@
   resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.6.6.tgz#0ee6300cc058db6b3551fa1c4bf73b83bb771312"
   integrity sha512-Sgr5lbboAUBo3eXCSPL4/KoVz3ROKquOjcctxmHIt+vol2DrqTQe3SwkKKuYhEiWB5kYa13YyopJ69deJ1irzQ==
 
-loose-envify@^1.0.0:
-  version "1.4.0"
-  resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
-  integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
-  dependencies:
-    js-tokens "^3.0.0 || ^4.0.0"
-
 loud-rejection@^1.0.0:
   version "1.6.0"
   resolved "https://registry.yarnpkg.com/loud-rejection/-/loud-rejection-1.6.0.tgz#5b46f80147edee578870f086d04821cf998e551f"
@@ -6504,6 +6621,11 @@
   dependencies:
     yallist "^3.0.2"
 
+lunr@^2.3.9:
+  version "2.3.9"
+  resolved "https://registry.yarnpkg.com/lunr/-/lunr-2.3.9.tgz#18b123142832337dd6e964df1a5a7707b25d35e1"
+  integrity sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==
+
 macos-release@^2.2.0:
   version "2.3.0"
   resolved "https://registry.yarnpkg.com/macos-release/-/macos-release-2.3.0.tgz#eb1930b036c0800adebccd5f17bc4c12de8bb71f"
@@ -6582,20 +6704,10 @@
   dependencies:
     object-visit "^1.0.0"
 
-matched@^0.4.4:
-  version "0.4.4"
-  resolved "https://registry.yarnpkg.com/matched/-/matched-0.4.4.tgz#56d7b7eb18033f0cf9bc52eb2090fac7dc1e89fa"
-  integrity sha1-Vte36xgDPwz5vFLrIJD6x9weifo=
-  dependencies:
-    arr-union "^3.1.0"
-    async-array-reduce "^0.2.0"
-    extend-shallow "^2.0.1"
-    fs-exists-sync "^0.1.0"
-    glob "^7.0.5"
-    has-glob "^0.1.1"
-    is-valid-glob "^0.3.0"
-    lazy-cache "^2.0.1"
-    resolve-dir "^0.1.0"
+marked@^2.0.1:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/marked/-/marked-2.0.1.tgz#5e7ed7009bfa5c95182e4eb696f85e948cefcee3"
+  integrity sha512-5+/fKgMv2hARmMW7DOpykr2iLhl0NgjyELk5yn92iE7z8Se1IS9n3UsFm86hFXIkvMBmVxki8+ckcpjBeyo/hw==
 
 md5.js@^1.3.4:
   version "1.3.5"
@@ -6669,10 +6781,10 @@
   resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60"
   integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==
 
-merge2@^1.2.3:
-  version "1.3.0"
-  resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.3.0.tgz#5b366ee83b2f1582c48f87e47cf1a9352103ca81"
-  integrity sha512-2j4DAdlBOkiSZIsaXk4mTE3sRS02yBHAtfy127xRV3bQUFqXkjHCHLW6Scv7DwNRbIWNHH8zpnz9zMaKXIdvYw==
+merge2@^1.2.3, merge2@^1.3.0:
+  version "1.4.1"
+  resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
+  integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
 
 methods@~1.1.2:
   version "1.1.2"
@@ -6756,7 +6868,7 @@
   resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a"
   integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=
 
-minimatch@3.0.4, minimatch@^3.0.4:
+minimatch@3.0.4, minimatch@^3.0.0, minimatch@^3.0.4:
   version "3.0.4"
   resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
   integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==
@@ -6771,7 +6883,7 @@
     arrify "^1.0.1"
     is-plain-obj "^1.1.0"
 
-minimist@0.0.8, minimist@~0.0.1:
+minimist@0.0.8:
   version "0.0.8"
   resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
   integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=
@@ -6834,16 +6946,6 @@
   dependencies:
     minimist "0.0.8"
 
-mocha-loader@^5.1.1:
-  version "5.1.1"
-  resolved "https://registry.yarnpkg.com/mocha-loader/-/mocha-loader-5.1.1.tgz#7348d33f25116fa1b9684679acbf062667b3b23c"
-  integrity sha512-WK3cgPf3N6ZZRuk7vUm4tt2BsuUMsjnbjpabNr32BMgEQyxcMxTD7jgbQdwIVZ99/YhHOhJmm5FRlEXu+/nLgA==
-  dependencies:
-    css-loader "^4.0.0"
-    loader-utils "^2.0.0"
-    schema-utils "^2.7.0"
-    style-loader "^1.2.1"
-
 mocha@^8.0.1:
   version "8.0.1"
   resolved "https://registry.yarnpkg.com/mocha/-/mocha-8.0.1.tgz#fe01f0530362df271aa8f99510447bc38b88d8ed"
@@ -6907,15 +7009,6 @@
   resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
   integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
 
-multi-entry-loader@^1.1.2:
-  version "1.1.2"
-  resolved "https://registry.yarnpkg.com/multi-entry-loader/-/multi-entry-loader-1.1.2.tgz#238085c85845726e50528955d19b04568fefcdb9"
-  integrity sha1-I4CFyFhFcm5QUolV0ZsEVo/vzbk=
-  dependencies:
-    glob-parent "^3.0.1"
-    loader-utils "^1.1.0"
-    matched "^0.4.4"
-
 multicast-dns-service-types@^1.1.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz#899f11d9686e5e05cb91b35d5f0e63b773cfc901"
@@ -7072,10 +7165,10 @@
   dependencies:
     process-on-spawn "^1.0.0"
 
-node-releases@^1.1.53:
-  version "1.1.57"
-  resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.57.tgz#f6754ce225fad0611e61228df3e09232e017ea19"
-  integrity sha512-ZQmnWS7adi61A9JsllJ2gdj2PauElcjnOwTp2O011iGzoakTxUsDGSe+6vD7wXbKdqhSFymC0OSx35aAMhrSdw==
+node-releases@^1.1.70:
+  version "1.1.71"
+  resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.71.tgz#cb1334b179896b1c89ecfdd4b725fb7bbdfc7dbb"
+  integrity sha512-zR6HoT6LrLCRBwukmrVbHv0EpEQjksO6GmFcZQQuCAy139BEsoVKPYnf3jongYW83fAa1torLGYwxxky/p28sg==
 
 "nopt@2 || 3":
   version "3.0.6"
@@ -7351,6 +7444,13 @@
   dependencies:
     mimic-fn "^2.1.0"
 
+onigasm@^2.2.5:
+  version "2.2.5"
+  resolved "https://registry.yarnpkg.com/onigasm/-/onigasm-2.2.5.tgz#cc4d2a79a0fa0b64caec1f4c7ea367585a676892"
+  integrity sha512-F+th54mPc0l1lp1ZcFMyL/jTs2Tlq4SqIHKIXGZOR/VkHkF9A7Fr5rRr5+ZG/lWeRsyrClLYRq7s/yFQ/XhWCA==
+  dependencies:
+    lru-cache "^5.1.1"
+
 opencollective-postinstall@^2.0.2:
   version "2.0.2"
   resolved "https://registry.yarnpkg.com/opencollective-postinstall/-/opencollective-postinstall-2.0.2.tgz#5657f1bede69b6e33a45939b061eb53d3c6c3a89"
@@ -7363,13 +7463,10 @@
   dependencies:
     is-wsl "^1.1.0"
 
-optimist@^0.6.1:
-  version "0.6.1"
-  resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686"
-  integrity sha1-2j6nRob6IaGaERwybpDrFaAZZoY=
-  dependencies:
-    minimist "~0.0.1"
-    wordwrap "~0.0.2"
+optimal-select@^4.0.1:
+  version "4.0.1"
+  resolved "https://registry.yarnpkg.com/optimal-select/-/optimal-select-4.0.1.tgz#47de7da7a39bb0949fd9af54c6f03571548f04c9"
+  integrity sha1-R959p6ObsJSf2a9UxvA1cVSPBMk=
 
 optionator@^0.8.1:
   version "0.8.3"
@@ -7407,7 +7504,7 @@
   resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27"
   integrity sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=
 
-os-homedir@^1.0.0, os-homedir@^1.0.1:
+os-homedir@^1.0.0:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3"
   integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M=
@@ -7774,7 +7871,7 @@
   resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
   integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=
 
-picomatch@^2.0.4, picomatch@^2.0.5, picomatch@^2.0.7:
+picomatch@^2.0.4, picomatch@^2.0.5, picomatch@^2.0.7, picomatch@^2.2.1:
   version "2.2.2"
   resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad"
   integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==
@@ -7869,62 +7966,6 @@
   resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab"
   integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=
 
-postcss-modules-extract-imports@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-2.0.0.tgz#818719a1ae1da325f9832446b01136eeb493cd7e"
-  integrity sha512-LaYLDNS4SG8Q5WAWqIJgdHPJrDDr/Lv775rMBFUbgjTz6j34lUznACHcdRWroPvXANP2Vj7yNK57vp9eFqzLWQ==
-  dependencies:
-    postcss "^7.0.5"
-
-postcss-modules-local-by-default@^3.0.3:
-  version "3.0.3"
-  resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-3.0.3.tgz#bb14e0cc78279d504dbdcbfd7e0ca28993ffbbb0"
-  integrity sha512-e3xDq+LotiGesympRlKNgaJ0PCzoUIdpH0dj47iWAui/kyTgh3CiAr1qP54uodmJhl6p9rN6BoNcdEDVJx9RDw==
-  dependencies:
-    icss-utils "^4.1.1"
-    postcss "^7.0.32"
-    postcss-selector-parser "^6.0.2"
-    postcss-value-parser "^4.1.0"
-
-postcss-modules-scope@^2.2.0:
-  version "2.2.0"
-  resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-2.2.0.tgz#385cae013cc7743f5a7d7602d1073a89eaae62ee"
-  integrity sha512-YyEgsTMRpNd+HmyC7H/mh3y+MeFWevy7V1evVhJWewmMbjDHIbZbOXICC2y+m1xI1UVfIT1HMW/O04Hxyu9oXQ==
-  dependencies:
-    postcss "^7.0.6"
-    postcss-selector-parser "^6.0.0"
-
-postcss-modules-values@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-3.0.0.tgz#5b5000d6ebae29b4255301b4a3a54574423e7f10"
-  integrity sha512-1//E5jCBrZ9DmRX+zCtmQtRSV6PV42Ix7Bzj9GbwJceduuf7IqP8MgeTXuRDHOWj2m0VzZD5+roFWDuU8RQjcg==
-  dependencies:
-    icss-utils "^4.0.0"
-    postcss "^7.0.6"
-
-postcss-selector-parser@^6.0.0, postcss-selector-parser@^6.0.2:
-  version "6.0.2"
-  resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.2.tgz#934cf799d016c83411859e09dcecade01286ec5c"
-  integrity sha512-36P2QR59jDTOAiIkqEprfJDsoNrvwFei3eCqKd1Y0tUsBimsq39BLp7RD+JWny3WgB1zGhJX8XVePwm9k4wdBg==
-  dependencies:
-    cssesc "^3.0.0"
-    indexes-of "^1.0.1"
-    uniq "^1.0.1"
-
-postcss-value-parser@^4.1.0:
-  version "4.1.0"
-  resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz#443f6a20ced6481a2bda4fa8532a6e55d789a2cb"
-  integrity sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==
-
-postcss@^7.0.14, postcss@^7.0.32, postcss@^7.0.5, postcss@^7.0.6:
-  version "7.0.32"
-  resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.32.tgz#4310d6ee347053da3433db2be492883d62cec59d"
-  integrity sha512-03eXong5NLnNCD05xscnGKGDZ98CyzoqPSMjOe6SuoQY7Z2hIj0Ld1g/O/UQRuOle2aRtiIRDg9tDcTGAkLfKw==
-  dependencies:
-    chalk "^2.4.2"
-    source-map "^0.6.1"
-    supports-color "^6.1.0"
-
 prelude-ls@^1.2.1:
   version "1.2.1"
   resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
@@ -7969,7 +8010,7 @@
   resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182"
   integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI=
 
-progress@^2.0.0:
+progress@^2.0.0, progress@^2.0.3:
   version "2.0.3"
   resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8"
   integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==
@@ -8231,6 +8272,15 @@
     normalize-package-data "^2.3.2"
     path-type "^3.0.0"
 
+read-pkg@^4.0.1:
+  version "4.0.1"
+  resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-4.0.1.tgz#963625378f3e1c4d48c85872b5a6ec7d5d093237"
+  integrity sha1-ljYlN48+HE1IyFhytabsfV0JMjc=
+  dependencies:
+    normalize-package-data "^2.3.2"
+    parse-json "^4.0.0"
+    pify "^3.0.0"
+
 read@1, read@~1.0.1:
   version "1.0.7"
   resolved "https://registry.yarnpkg.com/read/-/read-1.0.7.tgz#b3da19bd052431a97671d44a42634adf710b40c4"
@@ -8286,6 +8336,20 @@
   dependencies:
     picomatch "^2.0.7"
 
+readdirp@~3.5.0:
+  version "3.5.0"
+  resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.5.0.tgz#9ba74c019b15d365278d2e91bb8c48d7b4d42c9e"
+  integrity sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==
+  dependencies:
+    picomatch "^2.2.1"
+
+rechoir@^0.6.2:
+  version "0.6.2"
+  resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384"
+  integrity sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=
+  dependencies:
+    resolve "^1.1.6"
+
 redent@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/redent/-/redent-1.0.0.tgz#cf916ab1fd5f1f16dfb20822dd6ec7f730c2afde"
@@ -8347,10 +8411,10 @@
   resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.1.0.tgz#206d0ad0a5648cffbdb8ae46438f3dc51c9f78e2"
   integrity sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==
 
-regexpu-core@^4.7.0:
-  version "4.7.0"
-  resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.7.0.tgz#fcbf458c50431b0bb7b45d6967b8192d91f3d938"
-  integrity sha512-TQ4KXRnIn6tz6tjnrXEkD/sshygKH/j5KzK86X8MkeHyZ8qst/LZ89j3X4/8HEIfHANTFIP/AbXakeRhWIl5YQ==
+regexpu-core@^4.7.1:
+  version "4.7.1"
+  resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.7.1.tgz#2dea5a9a07233298fbf0db91fa9abc4c6e0f8ad6"
+  integrity sha512-ywH2VUraA44DZQuRKzARmw6S66mr48pQVva4LBeRhcOltJ6hExvWly5ZjFLYo67xbIxb6W1q4bAGtgfEl20zfQ==
   dependencies:
     regenerate "^1.4.0"
     regenerate-unicode-properties "^8.2.0"
@@ -8474,14 +8538,6 @@
   dependencies:
     resolve-from "^3.0.0"
 
-resolve-dir@^0.1.0:
-  version "0.1.1"
-  resolved "https://registry.yarnpkg.com/resolve-dir/-/resolve-dir-0.1.1.tgz#b219259a5602fac5c5c496ad894a6e8cc430261e"
-  integrity sha1-shklmlYC+sXFxJatiUpujMQwJh4=
-  dependencies:
-    expand-tilde "^1.2.2"
-    global-modules "^0.2.3"
-
 resolve-dir@^1.0.0, resolve-dir@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/resolve-dir/-/resolve-dir-1.0.1.tgz#79a40644c362be82f26effe739c9bb5382046f43"
@@ -8510,11 +8566,12 @@
   resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a"
   integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=
 
-resolve@^1.10.0, resolve@^1.10.1, resolve@^1.13.1, resolve@^1.15.0, resolve@^1.17.0, resolve@^1.3.2, resolve@^1.8.1:
-  version "1.17.0"
-  resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.17.0.tgz#b25941b54968231cc2d1bb76a79cb7f2c0bf8444"
-  integrity sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==
+resolve@^1.1.6, resolve@^1.10.0, resolve@^1.10.1, resolve@^1.13.1, resolve@^1.14.2, resolve@^1.15.0, resolve@^1.17.0:
+  version "1.20.0"
+  resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975"
+  integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==
   dependencies:
+    is-core-module "^2.2.0"
     path-parse "^1.0.6"
 
 restore-cursor@^2.0.0:
@@ -8540,6 +8597,11 @@
   resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b"
   integrity sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=
 
+reusify@^1.0.4:
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76"
+  integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==
+
 rimraf@2, rimraf@2.6.3, rimraf@^2.5.4, rimraf@^2.6.2, rimraf@^2.6.3:
   version "2.6.3"
   resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab"
@@ -8569,6 +8631,11 @@
   dependencies:
     is-promise "^2.1.0"
 
+run-parallel@^1.1.9:
+  version "1.1.9"
+  resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.1.9.tgz#c9dd3a7cf9f4b2c4b6244e173a6ed866e61dd679"
+  integrity sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q==
+
 run-queue@^1.0.0, run-queue@^1.0.3:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/run-queue/-/run-queue-1.0.3.tgz#e848396f057d223f24386924618e25694161ec47"
@@ -8576,10 +8643,10 @@
   dependencies:
     aproba "^1.1.1"
 
-rxjs@^6.3.3, rxjs@^6.4.0:
-  version "6.5.4"
-  resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.4.tgz#e0777fe0d184cec7872df147f303572d414e211c"
-  integrity sha512-naMQXcgEo3csAEGvw/NydRA0fuS2nDZJiw1YUWFKU7aPPAPGZEsD4Iimit96qwCieH6y614MCLYwdkrWx7z/7Q==
+rxjs@^6.3.3, rxjs@^6.4.0, rxjs@^6.5.2:
+  version "6.6.3"
+  resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.3.tgz#8ca84635c4daa900c0d3967a6ee7ac60271ee552"
+  integrity sha512-trsQc+xYYXZ3urjOiJOuCOa5N3jAZ3eiSpQB5hIT8zGlL2QfnHLJ2r7GMkBGuIausdJN1OneaI6gQlsqNHHmZQ==
   dependencies:
     tslib "^1.9.0"
 
@@ -8621,14 +8688,14 @@
     ajv-errors "^1.0.0"
     ajv-keywords "^3.1.0"
 
-schema-utils@^2.6.5, schema-utils@^2.6.6, schema-utils@^2.7.0:
-  version "2.7.0"
-  resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.7.0.tgz#17151f76d8eae67fbbf77960c33c676ad9f4efc7"
-  integrity sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==
+schema-utils@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.0.0.tgz#67502f6aa2b66a2d4032b4279a2944978a0913ef"
+  integrity sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==
   dependencies:
-    "@types/json-schema" "^7.0.4"
-    ajv "^6.12.2"
-    ajv-keywords "^3.4.1"
+    "@types/json-schema" "^7.0.6"
+    ajv "^6.12.5"
+    ajv-keywords "^3.5.2"
 
 select-hose@^2.0.0:
   version "2.0.0"
@@ -8662,7 +8729,7 @@
   resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e"
   integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==
 
-semver@^6.0.0, semver@^6.1.0, semver@^6.2.0, semver@^6.3.0:
+semver@^6.0.0, semver@^6.1.0, semver@^6.1.1, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0:
   version "6.3.0"
   resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
   integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
@@ -8734,13 +8801,6 @@
   resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
   integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc=
 
-set-getter@^0.1.0:
-  version "0.1.0"
-  resolved "https://registry.yarnpkg.com/set-getter/-/set-getter-0.1.0.tgz#d769c182c9d5a51f409145f2fba82e5e86e80376"
-  integrity sha1-12nBgsnVpR9AkUXy+6guXoboA3Y=
-  dependencies:
-    to-object-path "^0.3.0"
-
 set-value@^2.0.0, set-value@^2.0.1:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b"
@@ -8805,6 +8865,23 @@
   resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172"
   integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==
 
+shelljs@^0.8.4:
+  version "0.8.4"
+  resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.4.tgz#de7684feeb767f8716b326078a8a00875890e3c2"
+  integrity sha512-7gk3UZ9kOfPLIAbslLzyWeGiEqx9e3rxwZM0KE6EL8GlGwjym9Mrlx5/p33bWTu9YG6vcS4MBxYZDHYr5lr8BQ==
+  dependencies:
+    glob "^7.0.0"
+    interpret "^1.0.0"
+    rechoir "^0.6.2"
+
+shiki@^0.9.3:
+  version "0.9.3"
+  resolved "https://registry.yarnpkg.com/shiki/-/shiki-0.9.3.tgz#7bf7bcf3ed50ca525ec89cc09254abce4264d5ca"
+  integrity sha512-NEjg1mVbAUrzRv2eIcUt3TG7X9svX7l3n3F5/3OdFq+/BxUdmBOeKGiH4icZJBLHy354Shnj6sfBTemea2e7XA==
+  dependencies:
+    onigasm "^2.2.5"
+    vscode-textmate "^5.2.0"
+
 signal-exit@^3.0.0, signal-exit@^3.0.2:
   version "3.0.3"
   resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c"
@@ -8961,6 +9038,11 @@
   resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
   integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
 
+spawn-command@^0.0.2-1:
+  version "0.0.2-1"
+  resolved "https://registry.yarnpkg.com/spawn-command/-/spawn-command-0.0.2-1.tgz#62f5e9466981c1b796dc5929937e11c9c6921bd0"
+  integrity sha1-YvXpRmmBwbeW3Fkpk34RycaSG9A=
+
 spawn-wrap@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/spawn-wrap/-/spawn-wrap-2.0.0.tgz#103685b8b8f9b79771318827aa78650a610d457e"
@@ -9278,14 +9360,6 @@
     minimist "^1.2.0"
     through "^2.3.4"
 
-style-loader@^1.2.1:
-  version "1.2.1"
-  resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-1.2.1.tgz#c5cbbfbf1170d076cfdd86e0109c5bba114baa1a"
-  integrity sha512-ByHSTQvHLkWE9Ir5+lGbVOXhxX10fbprhLvdg96wedFZb4NDekDPxVKv5Fwmio+QcMlkkNfuK+5W1peQ5CUhZg==
-  dependencies:
-    loader-utils "^2.0.0"
-    schema-utils "^2.6.6"
-
 supports-color@6.1.0, supports-color@^6.1.0:
   version "6.1.0"
   resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3"
@@ -9541,6 +9615,11 @@
   dependencies:
     punycode "^2.1.1"
 
+tree-kill@^1.2.2:
+  version "1.2.2"
+  resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc"
+  integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==
+
 trim-newlines@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613"
@@ -9644,10 +9723,32 @@
   resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
   integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
 
-typescript@^3.9.7:
-  version "3.9.7"
-  resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.7.tgz#98d600a5ebdc38f40cb277522f12dc800e9e25fa"
-  integrity sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw==
+typedoc-default-themes@^0.12.9:
+  version "0.12.10"
+  resolved "https://registry.yarnpkg.com/typedoc-default-themes/-/typedoc-default-themes-0.12.10.tgz#614c4222fe642657f37693ea62cad4dafeddf843"
+  integrity sha512-fIS001cAYHkyQPidWXmHuhs8usjP5XVJjWB8oZGqkTowZaz3v7g3KDZeeqE82FBrmkAnIBOY3jgy7lnPnqATbA==
+
+typedoc@^0.20.5:
+  version "0.20.35"
+  resolved "https://registry.yarnpkg.com/typedoc/-/typedoc-0.20.35.tgz#c36996098cbeb2ef63d9d7991262a071b98336a3"
+  integrity sha512-7sNca19LXg2hgyGHq3b33tQ1YFApmd8aBDEzWQ2ry4VDkw/NdFWkysGiGRY1QckDCB0gVH8+MlXA4K71IB3azg==
+  dependencies:
+    colors "^1.4.0"
+    fs-extra "^9.1.0"
+    handlebars "^4.7.7"
+    lodash "^4.17.21"
+    lunr "^2.3.9"
+    marked "^2.0.1"
+    minimatch "^3.0.0"
+    progress "^2.0.3"
+    shelljs "^0.8.4"
+    shiki "^0.9.3"
+    typedoc-default-themes "^0.12.9"
+
+typescript@^4.2.3:
+  version "4.2.3"
+  resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.2.3.tgz#39062d8019912d43726298f09493d598048c1ce3"
+  integrity sha512-qOcYwxaByStAWrBf4x0fibwZvMRG+r4cQoTjbPtUlrWjBHbmCAww1i448U0GJ+3cNNEtebDteo/cHOR3xJ4wEw==
 
 uglify-js@^3.1.4:
   version "3.6.0"
@@ -9700,11 +9801,6 @@
     is-extendable "^0.1.1"
     set-value "^2.0.1"
 
-uniq@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff"
-  integrity sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=
-
 unique-filename@^1.1.1:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-1.1.1.tgz#1d69769369ada0583103a1e6ae87681b56573230"
@@ -9731,6 +9827,11 @@
   resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"
   integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==
 
+universalify@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717"
+  integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==
+
 unpipe@1.0.0, unpipe@~1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
@@ -9857,6 +9958,11 @@
   resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0"
   integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==
 
+vscode-textmate@^5.2.0:
+  version "5.4.0"
+  resolved "https://registry.yarnpkg.com/vscode-textmate/-/vscode-textmate-5.4.0.tgz#4b25ffc1f14ac3a90faf9a388c67a01d24257cd7"
+  integrity sha512-c0Q4zYZkcLizeYJ3hNyaVUM2AA8KDhNCA3JvXY8CeZSJuBdAy3bAvSbv46RClC4P3dSO9BdwhnKEx2zOo6vP/w==
+
 w3c-hr-time@^1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd"
@@ -10079,7 +10185,7 @@
   resolved "https://registry.yarnpkg.com/which-pm-runs/-/which-pm-runs-1.0.0.tgz#670b3afbc552e0b55df6b7780ca74615f23ad1cb"
   integrity sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs=
 
-which@1, which@^1.2.12, which@^1.2.14, which@^1.2.9, which@^1.3.1:
+which@1, which@^1.2.14, which@^1.2.9, which@^1.3.1:
   version "1.3.1"
   resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"
   integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==
@@ -10112,10 +10218,10 @@
   resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c"
   integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==
 
-wordwrap@~0.0.2:
-  version "0.0.3"
-  resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107"
-  integrity sha1-o9XabNXAvAAI03I0u68b7WMFkQc=
+wordwrap@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb"
+  integrity sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=
 
 worker-farm@^1.7.0:
   version "1.7.0"
@@ -10248,7 +10354,7 @@
   resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb"
   integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==
 
-xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.1:
+xtend@^4.0.0, xtend@~4.0.1:
   version "4.0.2"
   resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
   integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==