commit 9c089b16300e3d737f426588bafddc42f8255f97 Author: Hunter Johnston Date: Fri Jan 5 22:31:25 2024 -0500 initial commit diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..3897265 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,13 @@ +.DS_Store +node_modules +/build +/.svelte-kit +/package +.env +.env.* +!.env.example + +# Ignore files for PNPM, NPM and YARN +pnpm-lock.yaml +package-lock.json +yarn.lock diff --git a/.eslintrc.cjs b/.eslintrc.cjs new file mode 100644 index 0000000..0b75758 --- /dev/null +++ b/.eslintrc.cjs @@ -0,0 +1,31 @@ +/** @type { import("eslint").Linter.Config } */ +module.exports = { + root: true, + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/recommended', + 'plugin:svelte/recommended', + 'prettier' + ], + parser: '@typescript-eslint/parser', + plugins: ['@typescript-eslint'], + parserOptions: { + sourceType: 'module', + ecmaVersion: 2020, + extraFileExtensions: ['.svelte'] + }, + env: { + browser: true, + es2017: true, + node: true + }, + overrides: [ + { + files: ['*.svelte'], + parser: 'svelte-eslint-parser', + parserOptions: { + parser: '@typescript-eslint/parser' + } + } + ] +}; diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ac7211b --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +.DS_Store +node_modules +/build +/dist +/.svelte-kit +/package +.env +.env.* +!.env.example +vite.config.js.timestamp-* +vite.config.ts.timestamp-* diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..b6f27f1 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +engine-strict=true diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..cc41cea --- /dev/null +++ b/.prettierignore @@ -0,0 +1,4 @@ +# Ignore files for PNPM, NPM and YARN +pnpm-lock.yaml +package-lock.json +yarn.lock diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..9573023 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,8 @@ +{ + "useTabs": true, + "singleQuote": true, + "trailingComma": "none", + "printWidth": 100, + "plugins": ["prettier-plugin-svelte"], + "overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }] +} diff --git a/NOTICE.txt b/NOTICE.txt new file mode 100644 index 0000000..3184e30 --- /dev/null +++ b/NOTICE.txt @@ -0,0 +1,200 @@ +svelte-press-events +================= +The following is a list of sources from which code was used/modified in this codebase. + +------------------------------------------------------------------------------- +This codebase contains a modified portion of code from Adobe which can be obtained at: + + * SOURCE: + * https://www.github.com/adobe/react-spectrum/ + * LICENSE: + * https://github.com/adobe/react-spectrum/blob/main/LICENSE + + * SOURCE: + * https://www.npmjs.com/package/@adobe/react-spectrum-ui + * LICENSE: + * https://unpkg.com/@adobe/react-spectrum-ui@1.0.1/LICENSE + + * SOURCE: + * https://www.npmjs.com/package/@adobe/react-spectrum-workflow + * LICENSE: + * https://unpkg.com/@adobe/react-spectrum-workflow@1.0.1/LICENSE + + * SOURCE: + * https://www.npmjs.com/package/@adobe/react-spectrum-workflow-color + * LICENSE: + * https://unpkg.com/@adobe/react-spectrum-workflow-color@1.0.1/LICENSE + +------------------------------------------------------------------------------- +This codebase contains a portion of code from react which can be obtained at: + * SOURCE: + * https://github.com/facebook/react/tree/cc7c1aece46a6b69b41958d731e0fd27c94bfc6c/packages/react-interactions + + * LICENSE: + MIT License + + Copyright (c) Facebook, Inc. and its affiliates. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + +------------------------------------------------------------------------------- +This codebase contains a modified portion of code from react-window which can be obtained at: + * SOURCE: + * https://github.com/bvaughn/react-window/blob/master/src/createGridComponent.js + + * LICENSE: + The MIT License (MIT) + + Copyright (c) 2018 Brian Vaughn + + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +------------------------------------------------------------------------------- +This codebase contains a modified portion of code from focus-options-polyfill which can be obtained at: + * SOURCE: + * https://github.com/calvellido/focus-options-polyfill + + * LICENSE: + MIT License + + Copyright (c) 2018 Juan Valencia + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + +------------------------------------------------------------------------------- + +This codebase contains a portion of code that vuejs adapted from jest-dom which can be obtained at: + * SOURCE: + * https://github.com/vuejs/vue-test-utils-next/blob/master/src/utils/isElementVisible.ts + * LICENSE: + * https://github.com/vuejs/vue-test-utils-next/blob/master/LICENSE + + * SOURCE: + * https://github.com/testing-library/jest-dom/blob/main/src/to-be-visible.js + * LICENSE: + * https://github.com/testing-library/jest-dom/blob/main/LICENSE + +------------------------------------------------------------------------------ +This codebase contains a modified portion of code from ICU which can be obtained at: + * SOURCE: + * https://github.com/unicode-org/icu + + * LICENSE: + COPYRIGHT AND PERMISSION NOTICE (ICU 58 and later) + + Copyright © 1991-2020 Unicode, Inc. All rights reserved. + Distributed under the Terms of Use in https://www.unicode.org/copyright.html. + + Permission is hereby granted, free of charge, to any person obtaining + a copy of the Unicode data files and any associated documentation + (the "Data Files") or Unicode software and any associated documentation + (the "Software") to deal in the Data Files or Software + without restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, and/or sell copies of + the Data Files or Software, and to permit persons to whom the Data Files + or Software are furnished to do so, provided that either + (a) this copyright and permission notice appear with all copies + of the Data Files or Software, or + (b) this copyright and permission notice appear in associated + Documentation. + + THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF + ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT OF THIRD PARTY RIGHTS. + IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS + NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL + DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + PERFORMANCE OF THE DATA FILES OR SOFTWARE. + + Except as contained in this notice, the name of a copyright holder + shall not be used in advertising or otherwise to promote the sale, + use or other dealings in these Data Files or Software without prior + written authorization of the copyright holder. + +------------------------------------------------------------------------------- +This codebase contains a modified portion of code from the TC39 Temporal proposal which can be obtained at: + * SOURCE: + * https://github.com/tc39/proposal-temporal + + * LICENSE: + Copyright (c) 2017, 2018, 2019, 2020 + Ecma International. All rights reserved. + + All Software contained in this document ("Software") is protected by copyright + and is being made available under the "BSD License", included below. + + This Software may be subject to third party rights (rights from parties other + than Ecma International), including patent rights, and no licenses under such + third party rights are granted under this license even if the third party + concerned is a member of Ecma International. + + SEE THE ECMA CODE OF CONDUCT IN PATENT MATTERS AVAILABLE AT + https://ecma-international.org/memento/codeofconduct.htm + FOR INFORMATION REGARDING THE LICENSING OF PATENT CLAIMS THAT ARE REQUIRED TO + IMPLEMENT ECMA INTERNATIONAL STANDARDS. + + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + 3. Neither the name of the authors nor Ecma International may be used to + endorse or promote products derived from this software without specific prior + written permission. + + THIS SOFTWARE IS PROVIDED BY THE ECMA INTERNATIONAL "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL ECMA INTERNATIONAL BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..3e208c8 --- /dev/null +++ b/README.md @@ -0,0 +1,200 @@ +# Svelte Interactions + +At surface level, interactions may seem like a simple concept, but once you start peeling back the onion they are quite complex. For something as simple as a button to behave properly across all browsers and devices, you need more than just a `click` event handler. + +If you aren't convinced, it's highly recommended to read [this three-part blog post](https://react-spectrum.adobe.com/blog/building-a-button-part-1.html), which goes into detail about the complexities of interactions. + +This project is heavily inspired by that article and contains code derived from [React Aria's](https://react-spectrum.adobe.com) Interactions packages. It aims to provide a similar API for Svelte, in the form of [Svelte Actions](https://svelte.dev/docs/svelte-action) and eventually spreadable event attributes (once Svelte 5 is released). + +## Press Interaction + +The `press` interaction is used to implement buttons, links, and other pressable elements. It handles mouse, touch, and keyboard interactions, and ensures that the element is accessible to screen readers and keyboard users. + +No more having to wrangle all those event handlers yourself! Just and use the `press` action along with the different `PressEvents` to provide a consistent experience across all browsers and devices. + +#### Basic Usage + +```svelte + + + +``` + +### initPress + +Creates a new `press` interaction instance. Each element should have its own instance, as it maintains state for a single element. For example, if you had multiple buttons on a page: + +```svelte + + + + +``` + +#### PressConfig + +`initPress` takes in an optional `PressConfig` object, which can be used to customize the interaction. + +```ts +type PressConfig = PressEvents { + /** + * Whether the target is in a controlled press state + * (e.g. an overlay it triggers is open). + * + * @default false + */ + isPressed?: boolean; + + /** + * Whether the press events should be disabled. + * + * @default false + */ + isDisabled?: boolean; + + /** + * Whether the target should not receive focus on press. + * + * @default false + */ + preventFocusOnPress?: boolean; + + /** + * Whether press events should be canceled when the pointer + * leaves the target while pressed. By default, this is + * `false`, which means if the pointer returns back over + * the target while pressed, `pressstart`/`onPressStart` + * will be fired again. If set to `true`, the press is + * canceled when the pointer leaves the target and + * `pressstart`/`onPressStart` will not be fired if the + * pointer returns. + * + * @default false + */ + shouldCancelOnPointerExit?: boolean; + + /** + * Whether text selection should be enabled on the pressable element. + */ + allowTextSelectionOnPress?: boolean; +}; +``` + +The `PressConfig` object also includes handlers for all the different `PressEvents`. These are provided as a convenience, should you prefer to handle the events here rather than the custom `on:press*` events dispatched by the element with the `pressAction`. + +Be aware that if you use these handlers, the custom `on:press*` events will still be dispatched, so be sure you aren't handling the same event twice. + +```ts +type PressEvents = { + /** + * Handler that is called when the press is released + * over the target. + */ + onPress?: (e: PressEvent) => void; + + /** + * Handler that is called when a press interaction starts. + */ + onPressStart?: (e: PressEvent) => void; + + /** + * Handler that is called when a press interaction ends, + * either over the target or when the pointer leaves the target. + */ + onPressEnd?: (e: PressEvent) => void; + + /** + * Handler that is called when the press state changes. + */ + onPressChange?: (isPressed: boolean) => void; + + /** + * Handler that is called when a press is released over the + * target, regardless of whether it started on the target or + * not. + */ + onPressUp?: (e: PressEvent) => void; +}; +``` + +### Custom Events + +When you apply the `pressAction` to an element, it will dispatch custom `on:press*` events. You can use either these or the `PressEvents` handlers provided by `initPress` to handle the different press events. + +```ts +type CustomEvents = { + /** + * Event dispatched when the press is released over the target. + */ + 'on:press'?: (e: CustomEvent) => void; + + /** + * Event dispatched when a press interaction starts. + */ + 'on:pressstart'?: (e: CustomEvent) => void; + + /** + * Event dispatched when a press interaction ends, + * either over the target or when the pointer leaves the target. + */ + 'on:pressend'?: (e: CustomEvent) => void; + + /** + * Event dispatched when a press is released over the target, + * regardless of whether it started on the target or not. + */ + 'on:pressup'?: (e: CustomEvent) => void; +}; +``` + +#### PressEvent + +```ts +type PointerType = 'mouse' | 'pen' | 'touch' | 'keyboard' | 'virtual'; + +export interface PressEvent { + /** The type of press event being fired. */ + type: 'pressstart' | 'pressend' | 'pressup' | 'press'; + + /** The pointer type that triggered the press event. */ + pointerType: PointerType; + + /** The target element of the press event. */ + target: Element; + + /** Whether the shift keyboard modifier was held during the press event. */ + shiftKey: boolean; + + /** Whether the ctrl keyboard modifier was held during the press event. */ + ctrlKey: boolean; + + /** Whether the meta keyboard modifier was held during the press event. */ + metaKey: boolean; + + /** Whether the alt keyboard modifier was held during the press event. */ + altKey: boolean; + + /** + * By default, press events stop propagation to parent elements. + * In cases where a handler decides not to handle a specific event, + * it can call `continuePropagation()` to allow a parent to handle it. + */ + continuePropagation(): void; +} +``` diff --git a/package.json b/package.json new file mode 100644 index 0000000..a459a30 --- /dev/null +++ b/package.json @@ -0,0 +1,56 @@ +{ + "name": "svelte-interactions", + "version": "0.0.0", + "scripts": { + "dev": "vite dev", + "build": "vite build && npm run package", + "preview": "vite preview", + "package": "svelte-kit sync && svelte-package && publint", + "prepublishOnly": "npm run package", + "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", + "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", + "test": "vitest", + "lint": "prettier --check . && eslint .", + "format": "prettier --write ." + }, + "exports": { + ".": { + "types": "./dist/index.d.ts", + "svelte": "./dist/index.js" + } + }, + "files": [ + "dist", + "!dist/**/*.test.*", + "!dist/**/*.spec.*" + ], + "peerDependencies": { + "svelte": "^4.0.0" + }, + "devDependencies": { + "@sveltejs/adapter-auto": "^3.0.0", + "@sveltejs/kit": "^2.0.0", + "@sveltejs/package": "^2.0.0", + "@sveltejs/vite-plugin-svelte": "^3.0.0", + "@types/eslint": "8.56.0", + "@types/node": "^20.10.6", + "@typescript-eslint/eslint-plugin": "^6.0.0", + "@typescript-eslint/parser": "^6.0.0", + "eslint": "^8.56.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-svelte": "^2.35.1", + "prettier": "^3.1.1", + "prettier-plugin-svelte": "^3.1.2", + "publint": "^0.1.9", + "svelte": "^4.2.7", + "svelte-check": "^3.6.0", + "tslib": "^2.4.1", + "typescript": "^5.0.0", + "user-agent-data-types": "^0.4.2", + "vite": "^5.0.3", + "vitest": "^1.0.0" + }, + "svelte": "./dist/index.js", + "types": "./dist/index.d.ts", + "type": "module" +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..879b1c5 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,2586 @@ +lockfileVersion: '6.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +devDependencies: + '@sveltejs/adapter-auto': + specifier: ^3.0.0 + version: 3.1.0(@sveltejs/kit@2.0.6) + '@sveltejs/kit': + specifier: ^2.0.0 + version: 2.0.6(@sveltejs/vite-plugin-svelte@3.0.1)(svelte@4.2.8)(vite@5.0.11) + '@sveltejs/package': + specifier: ^2.0.0 + version: 2.2.5(svelte@4.2.8)(typescript@5.3.3) + '@sveltejs/vite-plugin-svelte': + specifier: ^3.0.0 + version: 3.0.1(svelte@4.2.8)(vite@5.0.11) + '@types/eslint': + specifier: 8.56.0 + version: 8.56.0 + '@types/node': + specifier: ^20.10.6 + version: 20.10.6 + '@typescript-eslint/eslint-plugin': + specifier: ^6.0.0 + version: 6.17.0(@typescript-eslint/parser@6.17.0)(eslint@8.56.0)(typescript@5.3.3) + '@typescript-eslint/parser': + specifier: ^6.0.0 + version: 6.17.0(eslint@8.56.0)(typescript@5.3.3) + eslint: + specifier: ^8.56.0 + version: 8.56.0 + eslint-config-prettier: + specifier: ^9.1.0 + version: 9.1.0(eslint@8.56.0) + eslint-plugin-svelte: + specifier: ^2.35.1 + version: 2.35.1(eslint@8.56.0)(svelte@4.2.8) + prettier: + specifier: ^3.1.1 + version: 3.1.1 + prettier-plugin-svelte: + specifier: ^3.1.2 + version: 3.1.2(prettier@3.1.1)(svelte@4.2.8) + publint: + specifier: ^0.1.9 + version: 0.1.16 + svelte: + specifier: ^4.2.7 + version: 4.2.8 + svelte-check: + specifier: ^3.6.0 + version: 3.6.2(postcss@8.4.33)(svelte@4.2.8) + tslib: + specifier: ^2.4.1 + version: 2.6.2 + typescript: + specifier: ^5.0.0 + version: 5.3.3 + user-agent-data-types: + specifier: ^0.4.2 + version: 0.4.2 + vite: + specifier: ^5.0.3 + version: 5.0.11(@types/node@20.10.6) + vitest: + specifier: ^1.0.0 + version: 1.1.3(@types/node@20.10.6) + +packages: + + /@aashutoshrathi/word-wrap@1.2.6: + resolution: {integrity: sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==} + engines: {node: '>=0.10.0'} + dev: true + + /@ampproject/remapping@2.2.1: + resolution: {integrity: sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==} + engines: {node: '>=6.0.0'} + dependencies: + '@jridgewell/gen-mapping': 0.3.3 + '@jridgewell/trace-mapping': 0.3.20 + dev: true + + /@esbuild/aix-ppc64@0.19.11: + resolution: {integrity: sha512-FnzU0LyE3ySQk7UntJO4+qIiQgI7KoODnZg5xzXIrFJlKd2P2gwHsHY4927xj9y5PJmJSzULiUCWmv7iWnNa7g==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-arm64@0.19.11: + resolution: {integrity: sha512-aiu7K/5JnLj//KOnOfEZ0D90obUkRzDMyqd/wNAUQ34m4YUPVhRZpnqKV9uqDGxT7cToSDnIHsGooyIczu9T+Q==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-arm@0.19.11: + resolution: {integrity: sha512-5OVapq0ClabvKvQ58Bws8+wkLCV+Rxg7tUVbo9xu034Nm536QTII4YzhaFriQ7rMrorfnFKUsArD2lqKbFY4vw==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-x64@0.19.11: + resolution: {integrity: sha512-eccxjlfGw43WYoY9QgB82SgGgDbibcqyDTlk3l3C0jOVHKxrjdc9CTwDUQd0vkvYg5um0OH+GpxYvp39r+IPOg==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/darwin-arm64@0.19.11: + resolution: {integrity: sha512-ETp87DRWuSt9KdDVkqSoKoLFHYTrkyz2+65fj9nfXsaV3bMhTCjtQfw3y+um88vGRKRiF7erPrh/ZuIdLUIVxQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@esbuild/darwin-x64@0.19.11: + resolution: {integrity: sha512-fkFUiS6IUK9WYUO/+22omwetaSNl5/A8giXvQlcinLIjVkxwTLSktbF5f/kJMftM2MJp9+fXqZ5ezS7+SALp4g==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@esbuild/freebsd-arm64@0.19.11: + resolution: {integrity: sha512-lhoSp5K6bxKRNdXUtHoNc5HhbXVCS8V0iZmDvyWvYq9S5WSfTIHU2UGjcGt7UeS6iEYp9eeymIl5mJBn0yiuxA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/freebsd-x64@0.19.11: + resolution: {integrity: sha512-JkUqn44AffGXitVI6/AbQdoYAq0TEullFdqcMY/PCUZ36xJ9ZJRtQabzMA+Vi7r78+25ZIBosLTOKnUXBSi1Kw==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-arm64@0.19.11: + resolution: {integrity: sha512-LneLg3ypEeveBSMuoa0kwMpCGmpu8XQUh+mL8XXwoYZ6Be2qBnVtcDI5azSvh7vioMDhoJFZzp9GWp9IWpYoUg==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-arm@0.19.11: + resolution: {integrity: sha512-3CRkr9+vCV2XJbjwgzjPtO8T0SZUmRZla+UL1jw+XqHZPkPgZiyWvbDvl9rqAN8Zl7qJF0O/9ycMtjU67HN9/Q==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-ia32@0.19.11: + resolution: {integrity: sha512-caHy++CsD8Bgq2V5CodbJjFPEiDPq8JJmBdeyZ8GWVQMjRD0sU548nNdwPNvKjVpamYYVL40AORekgfIubwHoA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-loong64@0.19.11: + resolution: {integrity: sha512-ppZSSLVpPrwHccvC6nQVZaSHlFsvCQyjnvirnVjbKSHuE5N24Yl8F3UwYUUR1UEPaFObGD2tSvVKbvR+uT1Nrg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-mips64el@0.19.11: + resolution: {integrity: sha512-B5x9j0OgjG+v1dF2DkH34lr+7Gmv0kzX6/V0afF41FkPMMqaQ77pH7CrhWeR22aEeHKaeZVtZ6yFwlxOKPVFyg==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-ppc64@0.19.11: + resolution: {integrity: sha512-MHrZYLeCG8vXblMetWyttkdVRjQlQUb/oMgBNurVEnhj4YWOr4G5lmBfZjHYQHHN0g6yDmCAQRR8MUHldvvRDA==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-riscv64@0.19.11: + resolution: {integrity: sha512-f3DY++t94uVg141dozDu4CCUkYW+09rWtaWfnb3bqe4w5NqmZd6nPVBm+qbz7WaHZCoqXqHz5p6CM6qv3qnSSQ==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-s390x@0.19.11: + resolution: {integrity: sha512-A5xdUoyWJHMMlcSMcPGVLzYzpcY8QP1RtYzX5/bS4dvjBGVxdhuiYyFwp7z74ocV7WDc0n1harxmpq2ePOjI0Q==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-x64@0.19.11: + resolution: {integrity: sha512-grbyMlVCvJSfxFQUndw5mCtWs5LO1gUlwP4CDi4iJBbVpZcqLVT29FxgGuBJGSzyOxotFG4LoO5X+M1350zmPA==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/netbsd-x64@0.19.11: + resolution: {integrity: sha512-13jvrQZJc3P230OhU8xgwUnDeuC/9egsjTkXN49b3GcS5BKvJqZn86aGM8W9pd14Kd+u7HuFBMVtrNGhh6fHEQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/openbsd-x64@0.19.11: + resolution: {integrity: sha512-ysyOGZuTp6SNKPE11INDUeFVVQFrhcNDVUgSQVDzqsqX38DjhPEPATpid04LCoUr2WXhQTEZ8ct/EgJCUDpyNw==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/sunos-x64@0.19.11: + resolution: {integrity: sha512-Hf+Sad9nVwvtxy4DXCZQqLpgmRTQqyFyhT3bZ4F2XlJCjxGmRFF0Shwn9rzhOYRB61w9VMXUkxlBy56dk9JJiQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-arm64@0.19.11: + resolution: {integrity: sha512-0P58Sbi0LctOMOQbpEOvOL44Ne0sqbS0XWHMvvrg6NE5jQ1xguCSSw9jQeUk2lfrXYsKDdOe6K+oZiwKPilYPQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-ia32@0.19.11: + resolution: {integrity: sha512-6YOrWS+sDJDmshdBIQU+Uoyh7pQKrdykdefC1avn76ss5c+RN6gut3LZA4E2cH5xUEp5/cA0+YxRaVtRAb0xBg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-x64@0.19.11: + resolution: {integrity: sha512-vfkhltrjCAb603XaFhqhAF4LGDi2M4OrCRrFusyQ+iTLQ/o60QQXxc9cZC/FFpihBI9N1Grn6SMKVJ4KP7Fuiw==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@eslint-community/eslint-utils@4.4.0(eslint@8.56.0): + resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + dependencies: + eslint: 8.56.0 + eslint-visitor-keys: 3.4.3 + dev: true + + /@eslint-community/regexpp@4.10.0: + resolution: {integrity: sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + dev: true + + /@eslint/eslintrc@2.1.4: + resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + ajv: 6.12.6 + debug: 4.3.4 + espree: 9.6.1 + globals: 13.24.0 + ignore: 5.3.0 + import-fresh: 3.3.0 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + dev: true + + /@eslint/js@8.56.0: + resolution: {integrity: sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dev: true + + /@humanwhocodes/config-array@0.11.13: + resolution: {integrity: sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==} + engines: {node: '>=10.10.0'} + dependencies: + '@humanwhocodes/object-schema': 2.0.1 + debug: 4.3.4 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + dev: true + + /@humanwhocodes/module-importer@1.0.1: + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + dev: true + + /@humanwhocodes/object-schema@2.0.1: + resolution: {integrity: sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==} + dev: true + + /@jest/schemas@29.6.3: + resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@sinclair/typebox': 0.27.8 + dev: true + + /@jridgewell/gen-mapping@0.3.3: + resolution: {integrity: sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==} + engines: {node: '>=6.0.0'} + dependencies: + '@jridgewell/set-array': 1.1.2 + '@jridgewell/sourcemap-codec': 1.4.15 + '@jridgewell/trace-mapping': 0.3.20 + dev: true + + /@jridgewell/resolve-uri@3.1.1: + resolution: {integrity: sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==} + engines: {node: '>=6.0.0'} + dev: true + + /@jridgewell/set-array@1.1.2: + resolution: {integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==} + engines: {node: '>=6.0.0'} + dev: true + + /@jridgewell/sourcemap-codec@1.4.15: + resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} + dev: true + + /@jridgewell/trace-mapping@0.3.20: + resolution: {integrity: sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==} + dependencies: + '@jridgewell/resolve-uri': 3.1.1 + '@jridgewell/sourcemap-codec': 1.4.15 + dev: true + + /@nodelib/fs.scandir@2.1.5: + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + dev: true + + /@nodelib/fs.stat@2.0.5: + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + dev: true + + /@nodelib/fs.walk@1.2.8: + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.16.0 + dev: true + + /@polka/url@1.0.0-next.24: + resolution: {integrity: sha512-2LuNTFBIO0m7kKIQvvPHN6UE63VjpmL9rnEEaOOaiSPbZK+zUOYIzBAWcED+3XYzhYsd/0mD57VdxAEqqV52CQ==} + dev: true + + /@rollup/rollup-android-arm-eabi@4.9.3: + resolution: {integrity: sha512-nvh9bB41vXEoKKvlWCGptpGt8EhrEwPQFDCY0VAto+R+qpSbaErPS3OjMZuXR8i/2UVw952Dtlnl2JFxH31Qvg==} + cpu: [arm] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-android-arm64@4.9.3: + resolution: {integrity: sha512-kffYCJ2RhDL1DlshLzYPyJtVeusHlA8Q1j6k6s4AEVKLq/3HfGa2ADDycLsmPo3OW83r4XtOPqRMbcFzFsEIzQ==} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-darwin-arm64@4.9.3: + resolution: {integrity: sha512-Fo7DR6Q9/+ztTyMBZ79+WJtb8RWZonyCgkBCjV51rW5K/dizBzImTW6HLC0pzmHaAevwM0jW1GtB5LCFE81mSw==} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-darwin-x64@4.9.3: + resolution: {integrity: sha512-5HcxDF9fqHucIlTiw/gmMb3Qv23L8bLCg904I74Q2lpl4j/20z9ogaD3tWkeguRuz+/17cuS321PT3PAuyjQdg==} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-arm-gnueabihf@4.9.3: + resolution: {integrity: sha512-cO6hKV+99D1V7uNJQn1chWaF9EGp7qV2N8sGH99q9Y62bsbN6Il55EwJppEWT+JiqDRg396vWCgwdHwje8itBQ==} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-arm64-gnu@4.9.3: + resolution: {integrity: sha512-xANyq6lVg6KMO8UUs0LjA4q7di3tPpDbzLPgVEU2/F1ngIZ54eli8Zdt3uUUTMXVbgTCafIO+JPeGMhu097i3w==} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-arm64-musl@4.9.3: + resolution: {integrity: sha512-TZJUfRTugVFATQToCMD8DNV6jv/KpSwhE1lLq5kXiQbBX3Pqw6dRKtzNkh5wcp0n09reBBq/7CGDERRw9KmE+g==} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-riscv64-gnu@4.9.3: + resolution: {integrity: sha512-4/QVaRyaB5tkEAGfjVvWrmWdPF6F2NoaoO5uEP7N0AyeBw7l8SeCWWKAGrbx/00PUdHrJVURJiYikazslSKttQ==} + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-x64-gnu@4.9.3: + resolution: {integrity: sha512-koLC6D3pj1YLZSkTy/jsk3HOadp7q2h6VQl/lPX854twOmmLNekHB6yuS+MkWcKdGGdW1JPuPBv/ZYhr5Yhtdg==} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-x64-musl@4.9.3: + resolution: {integrity: sha512-0OAkQ4HBp+JO2ip2Lgt/ShlrveOMzyhwt2D0KvqH28jFPqfZco28KSq76zymZwmU+F6GRojdxtQMJiNSXKNzeA==} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-win32-arm64-msvc@4.9.3: + resolution: {integrity: sha512-z5uvoMvdRWggigOnsb9OOCLERHV0ykRZoRB5O+URPZC9zM3pkoMg5fN4NKu2oHqgkzZtfx9u4njqqlYEzM1v9A==} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-win32-ia32-msvc@4.9.3: + resolution: {integrity: sha512-wxomCHjBVKws+O4N1WLnniKCXu7vkLtdq9Fl9CN/EbwEldojvUrkoHE/fBLZzC7IT/x12Ut6d6cRs4dFvqJkMg==} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-win32-x64-msvc@4.9.3: + resolution: {integrity: sha512-1Qf/qk/iEtx0aOi+AQQt5PBoW0mFngsm7bPuxHClC/hWh2hHBktR6ktSfUg5b5rC9v8hTwNmHE7lBWXkgqluUQ==} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@sinclair/typebox@0.27.8: + resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} + dev: true + + /@sveltejs/adapter-auto@3.1.0(@sveltejs/kit@2.0.6): + resolution: {integrity: sha512-igS5hqCwdiXWb8NoWzThKCVQQj9tKgUkbTtzfxBPgSLOyFjkiGNDX0SgCoY2QIUWBqOkfGTOqGlrW5Ynw9oUvw==} + peerDependencies: + '@sveltejs/kit': ^2.0.0 + dependencies: + '@sveltejs/kit': 2.0.6(@sveltejs/vite-plugin-svelte@3.0.1)(svelte@4.2.8)(vite@5.0.11) + import-meta-resolve: 4.0.0 + dev: true + + /@sveltejs/kit@2.0.6(@sveltejs/vite-plugin-svelte@3.0.1)(svelte@4.2.8)(vite@5.0.11): + resolution: {integrity: sha512-dnHtyjBLGXx+hrZQ9GuqLlSfTBixewJaByUVWai7LmB4dgV3FwkK155OltEgONDQW6KW64hLNS/uojdx3uC2/g==} + engines: {node: '>=18.13'} + hasBin: true + requiresBuild: true + peerDependencies: + '@sveltejs/vite-plugin-svelte': ^3.0.0 + svelte: ^4.0.0 || ^5.0.0-next.0 + vite: ^5.0.3 + dependencies: + '@sveltejs/vite-plugin-svelte': 3.0.1(svelte@4.2.8)(vite@5.0.11) + '@types/cookie': 0.6.0 + cookie: 0.6.0 + devalue: 4.3.2 + esm-env: 1.0.0 + kleur: 4.1.5 + magic-string: 0.30.5 + mrmime: 2.0.0 + sade: 1.8.1 + set-cookie-parser: 2.6.0 + sirv: 2.0.4 + svelte: 4.2.8 + tiny-glob: 0.2.9 + vite: 5.0.11(@types/node@20.10.6) + dev: true + + /@sveltejs/package@2.2.5(svelte@4.2.8)(typescript@5.3.3): + resolution: {integrity: sha512-H0dFDrp7b/tr4zrUzOfqPKHG8y6ceNlGKPfSpp4ym1kTPWP79Mea5rvDlcmsbOS26FmHN/vttubalBdOCGA6qA==} + engines: {node: ^16.14 || >=18} + hasBin: true + peerDependencies: + svelte: ^3.44.0 || ^4.0.0 + dependencies: + chokidar: 3.5.3 + kleur: 4.1.5 + sade: 1.8.1 + semver: 7.5.4 + svelte: 4.2.8 + svelte2tsx: 0.6.27(svelte@4.2.8)(typescript@5.3.3) + transitivePeerDependencies: + - typescript + dev: true + + /@sveltejs/vite-plugin-svelte-inspector@2.0.0(@sveltejs/vite-plugin-svelte@3.0.1)(svelte@4.2.8)(vite@5.0.11): + resolution: {integrity: sha512-gjr9ZFg1BSlIpfZ4PRewigrvYmHWbDrq2uvvPB1AmTWKuM+dI1JXQSUu2pIrYLb/QncyiIGkFDFKTwJ0XqQZZg==} + engines: {node: ^18.0.0 || >=20} + peerDependencies: + '@sveltejs/vite-plugin-svelte': ^3.0.0 + svelte: ^4.0.0 || ^5.0.0-next.0 + vite: ^5.0.0 + dependencies: + '@sveltejs/vite-plugin-svelte': 3.0.1(svelte@4.2.8)(vite@5.0.11) + debug: 4.3.4 + svelte: 4.2.8 + vite: 5.0.11(@types/node@20.10.6) + transitivePeerDependencies: + - supports-color + dev: true + + /@sveltejs/vite-plugin-svelte@3.0.1(svelte@4.2.8)(vite@5.0.11): + resolution: {integrity: sha512-CGURX6Ps+TkOovK6xV+Y2rn8JKa8ZPUHPZ/NKgCxAmgBrXReavzFl8aOSCj3kQ1xqT7yGJj53hjcV/gqwDAaWA==} + engines: {node: ^18.0.0 || >=20} + peerDependencies: + svelte: ^4.0.0 || ^5.0.0-next.0 + vite: ^5.0.0 + dependencies: + '@sveltejs/vite-plugin-svelte-inspector': 2.0.0(@sveltejs/vite-plugin-svelte@3.0.1)(svelte@4.2.8)(vite@5.0.11) + debug: 4.3.4 + deepmerge: 4.3.1 + kleur: 4.1.5 + magic-string: 0.30.5 + svelte: 4.2.8 + svelte-hmr: 0.15.3(svelte@4.2.8) + vite: 5.0.11(@types/node@20.10.6) + vitefu: 0.2.5(vite@5.0.11) + transitivePeerDependencies: + - supports-color + dev: true + + /@types/cookie@0.6.0: + resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==} + dev: true + + /@types/eslint@8.56.0: + resolution: {integrity: sha512-FlsN0p4FhuYRjIxpbdXovvHQhtlG05O1GG/RNWvdAxTboR438IOTwmrY/vLA+Xfgg06BTkP045M3vpFwTMv1dg==} + dependencies: + '@types/estree': 1.0.5 + '@types/json-schema': 7.0.15 + dev: true + + /@types/estree@1.0.5: + resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} + dev: true + + /@types/json-schema@7.0.15: + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + dev: true + + /@types/node@20.10.6: + resolution: {integrity: sha512-Vac8H+NlRNNlAmDfGUP7b5h/KA+AtWIzuXy0E6OyP8f1tCLYAtPvKRRDJjAPqhpCb0t6U2j7/xqAuLEebW2kiw==} + dependencies: + undici-types: 5.26.5 + dev: true + + /@types/pug@2.0.10: + resolution: {integrity: sha512-Sk/uYFOBAB7mb74XcpizmH0KOR2Pv3D2Hmrh1Dmy5BmK3MpdSa5kqZcg6EKBdklU0bFXX9gCfzvpnyUehrPIuA==} + dev: true + + /@types/semver@7.5.6: + resolution: {integrity: sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==} + dev: true + + /@typescript-eslint/eslint-plugin@6.17.0(@typescript-eslint/parser@6.17.0)(eslint@8.56.0)(typescript@5.3.3): + resolution: {integrity: sha512-Vih/4xLXmY7V490dGwBQJTpIZxH4ZFH6eCVmQ4RFkB+wmaCTDAx4dtgoWwMNGKLkqRY1L6rPqzEbjorRnDo4rQ==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + '@typescript-eslint/parser': ^6.0.0 || ^6.0.0-alpha + eslint: ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@eslint-community/regexpp': 4.10.0 + '@typescript-eslint/parser': 6.17.0(eslint@8.56.0)(typescript@5.3.3) + '@typescript-eslint/scope-manager': 6.17.0 + '@typescript-eslint/type-utils': 6.17.0(eslint@8.56.0)(typescript@5.3.3) + '@typescript-eslint/utils': 6.17.0(eslint@8.56.0)(typescript@5.3.3) + '@typescript-eslint/visitor-keys': 6.17.0 + debug: 4.3.4 + eslint: 8.56.0 + graphemer: 1.4.0 + ignore: 5.3.0 + natural-compare: 1.4.0 + semver: 7.5.4 + ts-api-utils: 1.0.3(typescript@5.3.3) + typescript: 5.3.3 + transitivePeerDependencies: + - supports-color + dev: true + + /@typescript-eslint/parser@6.17.0(eslint@8.56.0)(typescript@5.3.3): + resolution: {integrity: sha512-C4bBaX2orvhK+LlwrY8oWGmSl4WolCfYm513gEccdWZj0CwGadbIADb0FtVEcI+WzUyjyoBj2JRP8g25E6IB8A==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/scope-manager': 6.17.0 + '@typescript-eslint/types': 6.17.0 + '@typescript-eslint/typescript-estree': 6.17.0(typescript@5.3.3) + '@typescript-eslint/visitor-keys': 6.17.0 + debug: 4.3.4 + eslint: 8.56.0 + typescript: 5.3.3 + transitivePeerDependencies: + - supports-color + dev: true + + /@typescript-eslint/scope-manager@6.17.0: + resolution: {integrity: sha512-RX7a8lwgOi7am0k17NUO0+ZmMOX4PpjLtLRgLmT1d3lBYdWH4ssBUbwdmc5pdRX8rXon8v9x8vaoOSpkHfcXGA==} + engines: {node: ^16.0.0 || >=18.0.0} + dependencies: + '@typescript-eslint/types': 6.17.0 + '@typescript-eslint/visitor-keys': 6.17.0 + dev: true + + /@typescript-eslint/type-utils@6.17.0(eslint@8.56.0)(typescript@5.3.3): + resolution: {integrity: sha512-hDXcWmnbtn4P2B37ka3nil3yi3VCQO2QEB9gBiHJmQp5wmyQWqnjA85+ZcE8c4FqnaB6lBwMrPkgd4aBYz3iNg==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/typescript-estree': 6.17.0(typescript@5.3.3) + '@typescript-eslint/utils': 6.17.0(eslint@8.56.0)(typescript@5.3.3) + debug: 4.3.4 + eslint: 8.56.0 + ts-api-utils: 1.0.3(typescript@5.3.3) + typescript: 5.3.3 + transitivePeerDependencies: + - supports-color + dev: true + + /@typescript-eslint/types@6.17.0: + resolution: {integrity: sha512-qRKs9tvc3a4RBcL/9PXtKSehI/q8wuU9xYJxe97WFxnzH8NWWtcW3ffNS+EWg8uPvIerhjsEZ+rHtDqOCiH57A==} + engines: {node: ^16.0.0 || >=18.0.0} + dev: true + + /@typescript-eslint/typescript-estree@6.17.0(typescript@5.3.3): + resolution: {integrity: sha512-gVQe+SLdNPfjlJn5VNGhlOhrXz4cajwFd5kAgWtZ9dCZf4XJf8xmgCTLIqec7aha3JwgLI2CK6GY1043FRxZwg==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/types': 6.17.0 + '@typescript-eslint/visitor-keys': 6.17.0 + debug: 4.3.4 + globby: 11.1.0 + is-glob: 4.0.3 + minimatch: 9.0.3 + semver: 7.5.4 + ts-api-utils: 1.0.3(typescript@5.3.3) + typescript: 5.3.3 + transitivePeerDependencies: + - supports-color + dev: true + + /@typescript-eslint/utils@6.17.0(eslint@8.56.0)(typescript@5.3.3): + resolution: {integrity: sha512-LofsSPjN/ITNkzV47hxas2JCsNCEnGhVvocfyOcLzT9c/tSZE7SfhS/iWtzP1lKNOEfLhRTZz6xqI8N2RzweSQ==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@8.56.0) + '@types/json-schema': 7.0.15 + '@types/semver': 7.5.6 + '@typescript-eslint/scope-manager': 6.17.0 + '@typescript-eslint/types': 6.17.0 + '@typescript-eslint/typescript-estree': 6.17.0(typescript@5.3.3) + eslint: 8.56.0 + semver: 7.5.4 + transitivePeerDependencies: + - supports-color + - typescript + dev: true + + /@typescript-eslint/visitor-keys@6.17.0: + resolution: {integrity: sha512-H6VwB/k3IuIeQOyYczyyKN8wH6ed8EwliaYHLxOIhyF0dYEIsN8+Bk3GE19qafeMKyZJJHP8+O1HiFhFLUNKSg==} + engines: {node: ^16.0.0 || >=18.0.0} + dependencies: + '@typescript-eslint/types': 6.17.0 + eslint-visitor-keys: 3.4.3 + dev: true + + /@ungap/structured-clone@1.2.0: + resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} + dev: true + + /@vitest/expect@1.1.3: + resolution: {integrity: sha512-MnJqsKc1Ko04lksF9XoRJza0bGGwTtqfbyrsYv5on4rcEkdo+QgUdITenBQBUltKzdxW7K3rWh+nXRULwsdaVg==} + dependencies: + '@vitest/spy': 1.1.3 + '@vitest/utils': 1.1.3 + chai: 4.4.0 + dev: true + + /@vitest/runner@1.1.3: + resolution: {integrity: sha512-Va2XbWMnhSdDEh/OFxyUltgQuuDRxnarK1hW5QNN4URpQrqq6jtt8cfww/pQQ4i0LjoYxh/3bYWvDFlR9tU73g==} + dependencies: + '@vitest/utils': 1.1.3 + p-limit: 5.0.0 + pathe: 1.1.1 + dev: true + + /@vitest/snapshot@1.1.3: + resolution: {integrity: sha512-U0r8pRXsLAdxSVAyGNcqOU2H3Z4Y2dAAGGelL50O0QRMdi1WWeYHdrH/QWpN1e8juWfVKsb8B+pyJwTC+4Gy9w==} + dependencies: + magic-string: 0.30.5 + pathe: 1.1.1 + pretty-format: 29.7.0 + dev: true + + /@vitest/spy@1.1.3: + resolution: {integrity: sha512-Ec0qWyGS5LhATFQtldvChPTAHv08yHIOZfiNcjwRQbFPHpkih0md9KAbs7TfeIfL7OFKoe7B/6ukBTqByubXkQ==} + dependencies: + tinyspy: 2.2.0 + dev: true + + /@vitest/utils@1.1.3: + resolution: {integrity: sha512-Dyt3UMcdElTll2H75vhxfpZu03uFpXRCHxWnzcrFjZxT1kTbq8ALUYIeBgGolo1gldVdI0YSlQRacsqxTwNqwg==} + dependencies: + diff-sequences: 29.6.3 + estree-walker: 3.0.3 + loupe: 2.3.7 + pretty-format: 29.7.0 + dev: true + + /acorn-jsx@5.3.2(acorn@8.11.3): + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + dependencies: + acorn: 8.11.3 + dev: true + + /acorn-walk@8.3.1: + resolution: {integrity: sha512-TgUZgYvqZprrl7YldZNoa9OciCAyZR+Ejm9eXzKCmjsF5IKp/wgQ7Z/ZpjpGTIUPwrHQIcYeI8qDh4PsEwxMbw==} + engines: {node: '>=0.4.0'} + dev: true + + /acorn@8.11.3: + resolution: {integrity: sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==} + engines: {node: '>=0.4.0'} + hasBin: true + dev: true + + /ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + dev: true + + /ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + dev: true + + /ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + dependencies: + color-convert: 2.0.1 + dev: true + + /ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + dev: true + + /anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + dev: true + + /argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + dev: true + + /aria-query@5.3.0: + resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} + dependencies: + dequal: 2.0.3 + dev: true + + /array-union@2.1.0: + resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} + engines: {node: '>=8'} + dev: true + + /assertion-error@1.1.0: + resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} + dev: true + + /axobject-query@3.2.1: + resolution: {integrity: sha512-jsyHu61e6N4Vbz/v18DHwWYKK0bSWLqn47eeDSKPB7m8tqMHF9YJ+mhIk2lVteyZrY8tnSj/jHOv4YiTCuCJgg==} + dependencies: + dequal: 2.0.3 + dev: true + + /balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + dev: true + + /binary-extensions@2.2.0: + resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} + engines: {node: '>=8'} + dev: true + + /brace-expansion@1.1.11: + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + dev: true + + /brace-expansion@2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + dependencies: + balanced-match: 1.0.2 + dev: true + + /braces@3.0.2: + resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} + engines: {node: '>=8'} + dependencies: + fill-range: 7.0.1 + dev: true + + /buffer-crc32@0.2.13: + resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} + dev: true + + /cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + dev: true + + /callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + dev: true + + /chai@4.4.0: + resolution: {integrity: sha512-x9cHNq1uvkCdU+5xTkNh5WtgD4e4yDFCsp9jVc7N7qVeKeftv3gO/ZrviX5d+3ZfxdYnZXZYujjRInu1RogU6A==} + engines: {node: '>=4'} + dependencies: + assertion-error: 1.1.0 + check-error: 1.0.3 + deep-eql: 4.1.3 + get-func-name: 2.0.2 + loupe: 2.3.7 + pathval: 1.1.1 + type-detect: 4.0.8 + dev: true + + /chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + dev: true + + /check-error@1.0.3: + resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==} + dependencies: + get-func-name: 2.0.2 + dev: true + + /chokidar@3.5.3: + resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} + engines: {node: '>= 8.10.0'} + dependencies: + anymatch: 3.1.3 + braces: 3.0.2 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + dev: true + + /code-red@1.0.4: + resolution: {integrity: sha512-7qJWqItLA8/VPVlKJlFXU+NBlo/qyfs39aJcuMT/2ere32ZqvF5OSxgdM5xOfJJ7O429gg2HM47y8v9P+9wrNw==} + dependencies: + '@jridgewell/sourcemap-codec': 1.4.15 + '@types/estree': 1.0.5 + acorn: 8.11.3 + estree-walker: 3.0.3 + periscopic: 3.1.0 + dev: true + + /color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + dependencies: + color-name: 1.1.4 + dev: true + + /color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + dev: true + + /concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + dev: true + + /cookie@0.6.0: + resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==} + engines: {node: '>= 0.6'} + dev: true + + /cross-spawn@7.0.3: + resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} + engines: {node: '>= 8'} + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + dev: true + + /css-tree@2.3.1: + resolution: {integrity: sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} + dependencies: + mdn-data: 2.0.30 + source-map-js: 1.0.2 + dev: true + + /cssesc@3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true + dev: true + + /debug@4.3.4: + resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: 2.1.2 + dev: true + + /dedent-js@1.0.1: + resolution: {integrity: sha512-OUepMozQULMLUmhxS95Vudo0jb0UchLimi3+pQ2plj61Fcy8axbP9hbiD4Sz6DPqn6XG3kfmziVfQ1rSys5AJQ==} + dev: true + + /deep-eql@4.1.3: + resolution: {integrity: sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==} + engines: {node: '>=6'} + dependencies: + type-detect: 4.0.8 + dev: true + + /deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + dev: true + + /deepmerge@4.3.1: + resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} + engines: {node: '>=0.10.0'} + dev: true + + /dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + dev: true + + /detect-indent@6.1.0: + resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==} + engines: {node: '>=8'} + dev: true + + /devalue@4.3.2: + resolution: {integrity: sha512-KqFl6pOgOW+Y6wJgu80rHpo2/3H07vr8ntR9rkkFIRETewbf5GaYYcakYfiKz89K+sLsuPkQIZaXDMjUObZwWg==} + dev: true + + /diff-sequences@29.6.3: + resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dev: true + + /dir-glob@3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} + engines: {node: '>=8'} + dependencies: + path-type: 4.0.0 + dev: true + + /doctrine@3.0.0: + resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} + engines: {node: '>=6.0.0'} + dependencies: + esutils: 2.0.3 + dev: true + + /es6-promise@3.3.1: + resolution: {integrity: sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg==} + dev: true + + /esbuild@0.19.11: + resolution: {integrity: sha512-HJ96Hev2hX/6i5cDVwcqiJBBtuo9+FeIJOtZ9W1kA5M6AMJRHUZlpYZ1/SbEwtO0ioNAW8rUooVpC/WehY2SfA==} + engines: {node: '>=12'} + hasBin: true + requiresBuild: true + optionalDependencies: + '@esbuild/aix-ppc64': 0.19.11 + '@esbuild/android-arm': 0.19.11 + '@esbuild/android-arm64': 0.19.11 + '@esbuild/android-x64': 0.19.11 + '@esbuild/darwin-arm64': 0.19.11 + '@esbuild/darwin-x64': 0.19.11 + '@esbuild/freebsd-arm64': 0.19.11 + '@esbuild/freebsd-x64': 0.19.11 + '@esbuild/linux-arm': 0.19.11 + '@esbuild/linux-arm64': 0.19.11 + '@esbuild/linux-ia32': 0.19.11 + '@esbuild/linux-loong64': 0.19.11 + '@esbuild/linux-mips64el': 0.19.11 + '@esbuild/linux-ppc64': 0.19.11 + '@esbuild/linux-riscv64': 0.19.11 + '@esbuild/linux-s390x': 0.19.11 + '@esbuild/linux-x64': 0.19.11 + '@esbuild/netbsd-x64': 0.19.11 + '@esbuild/openbsd-x64': 0.19.11 + '@esbuild/sunos-x64': 0.19.11 + '@esbuild/win32-arm64': 0.19.11 + '@esbuild/win32-ia32': 0.19.11 + '@esbuild/win32-x64': 0.19.11 + dev: true + + /escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + dev: true + + /eslint-compat-utils@0.1.2(eslint@8.56.0): + resolution: {integrity: sha512-Jia4JDldWnFNIru1Ehx1H5s9/yxiRHY/TimCuUc0jNexew3cF1gI6CYZil1ociakfWO3rRqFjl1mskBblB3RYg==} + engines: {node: '>=12'} + peerDependencies: + eslint: '>=6.0.0' + dependencies: + eslint: 8.56.0 + dev: true + + /eslint-config-prettier@9.1.0(eslint@8.56.0): + resolution: {integrity: sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==} + hasBin: true + peerDependencies: + eslint: '>=7.0.0' + dependencies: + eslint: 8.56.0 + dev: true + + /eslint-plugin-svelte@2.35.1(eslint@8.56.0)(svelte@4.2.8): + resolution: {integrity: sha512-IF8TpLnROSGy98Z3NrsKXWDSCbNY2ReHDcrYTuXZMbfX7VmESISR78TWgO9zdg4Dht1X8coub5jKwHzP0ExRug==} + engines: {node: ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0-0 + svelte: ^3.37.0 || ^4.0.0 + peerDependenciesMeta: + svelte: + optional: true + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@8.56.0) + '@jridgewell/sourcemap-codec': 1.4.15 + debug: 4.3.4 + eslint: 8.56.0 + eslint-compat-utils: 0.1.2(eslint@8.56.0) + esutils: 2.0.3 + known-css-properties: 0.29.0 + postcss: 8.4.33 + postcss-load-config: 3.1.4(postcss@8.4.33) + postcss-safe-parser: 6.0.0(postcss@8.4.33) + postcss-selector-parser: 6.0.15 + semver: 7.5.4 + svelte: 4.2.8 + svelte-eslint-parser: 0.33.1(svelte@4.2.8) + transitivePeerDependencies: + - supports-color + - ts-node + dev: true + + /eslint-scope@7.2.2: + resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + dev: true + + /eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dev: true + + /eslint@8.56.0: + resolution: {integrity: sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + hasBin: true + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@8.56.0) + '@eslint-community/regexpp': 4.10.0 + '@eslint/eslintrc': 2.1.4 + '@eslint/js': 8.56.0 + '@humanwhocodes/config-array': 0.11.13 + '@humanwhocodes/module-importer': 1.0.1 + '@nodelib/fs.walk': 1.2.8 + '@ungap/structured-clone': 1.2.0 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.3 + debug: 4.3.4 + doctrine: 3.0.0 + escape-string-regexp: 4.0.0 + eslint-scope: 7.2.2 + eslint-visitor-keys: 3.4.3 + espree: 9.6.1 + esquery: 1.5.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 6.0.1 + find-up: 5.0.0 + glob-parent: 6.0.2 + globals: 13.24.0 + graphemer: 1.4.0 + ignore: 5.3.0 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + is-path-inside: 3.0.3 + js-yaml: 4.1.0 + json-stable-stringify-without-jsonify: 1.0.1 + levn: 0.4.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.3 + strip-ansi: 6.0.1 + text-table: 0.2.0 + transitivePeerDependencies: + - supports-color + dev: true + + /esm-env@1.0.0: + resolution: {integrity: sha512-Cf6VksWPsTuW01vU9Mk/3vRue91Zevka5SjyNf3nEpokFRuqt/KjUQoGAwq9qMmhpLTHmXzSIrFRw8zxWzmFBA==} + dev: true + + /espree@9.6.1: + resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + acorn: 8.11.3 + acorn-jsx: 5.3.2(acorn@8.11.3) + eslint-visitor-keys: 3.4.3 + dev: true + + /esquery@1.5.0: + resolution: {integrity: sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==} + engines: {node: '>=0.10'} + dependencies: + estraverse: 5.3.0 + dev: true + + /esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + dependencies: + estraverse: 5.3.0 + dev: true + + /estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + dev: true + + /estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + dependencies: + '@types/estree': 1.0.5 + dev: true + + /esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + dev: true + + /execa@8.0.1: + resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} + engines: {node: '>=16.17'} + dependencies: + cross-spawn: 7.0.3 + get-stream: 8.0.1 + human-signals: 5.0.0 + is-stream: 3.0.0 + merge-stream: 2.0.0 + npm-run-path: 5.2.0 + onetime: 6.0.0 + signal-exit: 4.1.0 + strip-final-newline: 3.0.0 + dev: true + + /fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + dev: true + + /fast-glob@3.3.2: + resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} + engines: {node: '>=8.6.0'} + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.5 + dev: true + + /fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + dev: true + + /fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + dev: true + + /fastq@1.16.0: + resolution: {integrity: sha512-ifCoaXsDrsdkWTtiNJX5uzHDsrck5TzfKKDcuFFTIrrc/BS076qgEIfoIy1VeZqViznfKiysPYTh/QeHtnIsYA==} + dependencies: + reusify: 1.0.4 + dev: true + + /file-entry-cache@6.0.1: + resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} + engines: {node: ^10.12.0 || >=12.0.0} + dependencies: + flat-cache: 3.2.0 + dev: true + + /fill-range@7.0.1: + resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} + engines: {node: '>=8'} + dependencies: + to-regex-range: 5.0.1 + dev: true + + /find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + dev: true + + /flat-cache@3.2.0: + resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} + engines: {node: ^10.12.0 || >=12.0.0} + dependencies: + flatted: 3.2.9 + keyv: 4.5.4 + rimraf: 3.0.2 + dev: true + + /flatted@3.2.9: + resolution: {integrity: sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==} + dev: true + + /fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + dev: true + + /fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /get-func-name@2.0.2: + resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==} + dev: true + + /get-stream@8.0.1: + resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} + engines: {node: '>=16'} + dev: true + + /glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + dependencies: + is-glob: 4.0.3 + dev: true + + /glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + dependencies: + is-glob: 4.0.3 + dev: true + + /glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + dev: true + + /glob@8.1.0: + resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==} + engines: {node: '>=12'} + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 5.1.6 + once: 1.4.0 + dev: true + + /globals@13.24.0: + resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} + engines: {node: '>=8'} + dependencies: + type-fest: 0.20.2 + dev: true + + /globalyzer@0.1.0: + resolution: {integrity: sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q==} + dev: true + + /globby@11.1.0: + resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} + engines: {node: '>=10'} + dependencies: + array-union: 2.1.0 + dir-glob: 3.0.1 + fast-glob: 3.3.2 + ignore: 5.3.0 + merge2: 1.4.1 + slash: 3.0.0 + dev: true + + /globrex@0.1.2: + resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} + dev: true + + /graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + dev: true + + /graphemer@1.4.0: + resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + dev: true + + /has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + dev: true + + /human-signals@5.0.0: + resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} + engines: {node: '>=16.17.0'} + dev: true + + /ignore-walk@5.0.1: + resolution: {integrity: sha512-yemi4pMf51WKT7khInJqAvsIGzoqYXblnsz0ql8tM+yi1EKYTY1evX4NAbJrLL/Aanr2HyZeluqU+Oi7MGHokw==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + dependencies: + minimatch: 5.1.6 + dev: true + + /ignore@5.3.0: + resolution: {integrity: sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==} + engines: {node: '>= 4'} + dev: true + + /import-fresh@3.3.0: + resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} + engines: {node: '>=6'} + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + dev: true + + /import-meta-resolve@4.0.0: + resolution: {integrity: sha512-okYUR7ZQPH+efeuMJGlq4f8ubUgO50kByRPyt/Cy1Io4PSRsPjxME+YlVaCOx+NIToW7hCsZNFJyTPFFKepRSA==} + dev: true + + /imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + dev: true + + /inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + dev: true + + /inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + dev: true + + /is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + dependencies: + binary-extensions: 2.2.0 + dev: true + + /is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + dev: true + + /is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + dependencies: + is-extglob: 2.1.1 + dev: true + + /is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + dev: true + + /is-path-inside@3.0.3: + resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} + engines: {node: '>=8'} + dev: true + + /is-reference@3.0.2: + resolution: {integrity: sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==} + dependencies: + '@types/estree': 1.0.5 + dev: true + + /is-stream@3.0.0: + resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dev: true + + /isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + dev: true + + /js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + dependencies: + argparse: 2.0.1 + dev: true + + /json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + dev: true + + /json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + dev: true + + /json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + dev: true + + /jsonc-parser@3.2.0: + resolution: {integrity: sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==} + dev: true + + /keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + dependencies: + json-buffer: 3.0.1 + dev: true + + /kleur@4.1.5: + resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} + engines: {node: '>=6'} + dev: true + + /known-css-properties@0.29.0: + resolution: {integrity: sha512-Ne7wqW7/9Cz54PDt4I3tcV+hAyat8ypyOGzYRJQfdxnnjeWsTxt1cy8pjvvKeI5kfXuyvULyeeAvwvvtAX3ayQ==} + dev: true + + /levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + dev: true + + /lilconfig@2.1.0: + resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} + engines: {node: '>=10'} + dev: true + + /local-pkg@0.5.0: + resolution: {integrity: sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==} + engines: {node: '>=14'} + dependencies: + mlly: 1.4.2 + pkg-types: 1.0.3 + dev: true + + /locate-character@3.0.0: + resolution: {integrity: sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==} + dev: true + + /locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + dependencies: + p-locate: 5.0.0 + dev: true + + /lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + dev: true + + /loupe@2.3.7: + resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==} + dependencies: + get-func-name: 2.0.2 + dev: true + + /lower-case@2.0.2: + resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} + dependencies: + tslib: 2.6.2 + dev: true + + /lru-cache@6.0.0: + resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} + engines: {node: '>=10'} + dependencies: + yallist: 4.0.0 + dev: true + + /magic-string@0.30.5: + resolution: {integrity: sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==} + engines: {node: '>=12'} + dependencies: + '@jridgewell/sourcemap-codec': 1.4.15 + dev: true + + /mdn-data@2.0.30: + resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==} + dev: true + + /merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + dev: true + + /merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + dev: true + + /micromatch@4.0.5: + resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} + engines: {node: '>=8.6'} + dependencies: + braces: 3.0.2 + picomatch: 2.3.1 + dev: true + + /mimic-fn@4.0.0: + resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} + engines: {node: '>=12'} + dev: true + + /min-indent@1.0.1: + resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} + engines: {node: '>=4'} + dev: true + + /minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + dependencies: + brace-expansion: 1.1.11 + dev: true + + /minimatch@5.1.6: + resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} + engines: {node: '>=10'} + dependencies: + brace-expansion: 2.0.1 + dev: true + + /minimatch@9.0.3: + resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==} + engines: {node: '>=16 || 14 >=14.17'} + dependencies: + brace-expansion: 2.0.1 + dev: true + + /minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + dev: true + + /mkdirp@0.5.6: + resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} + hasBin: true + dependencies: + minimist: 1.2.8 + dev: true + + /mlly@1.4.2: + resolution: {integrity: sha512-i/Ykufi2t1EZ6NaPLdfnZk2AX8cs0d+mTzVKuPfqPKPatxLApaBoxJQ9x1/uckXtrS/U5oisPMDkNs0yQTaBRg==} + dependencies: + acorn: 8.11.3 + pathe: 1.1.1 + pkg-types: 1.0.3 + ufo: 1.3.2 + dev: true + + /mri@1.2.0: + resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} + engines: {node: '>=4'} + dev: true + + /mrmime@2.0.0: + resolution: {integrity: sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==} + engines: {node: '>=10'} + dev: true + + /ms@2.1.2: + resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + dev: true + + /nanoid@3.3.7: + resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + dev: true + + /natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + dev: true + + /no-case@3.0.4: + resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} + dependencies: + lower-case: 2.0.2 + tslib: 2.6.2 + dev: true + + /normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + dev: true + + /npm-bundled@2.0.1: + resolution: {integrity: sha512-gZLxXdjEzE/+mOstGDqR6b0EkhJ+kM6fxM6vUuckuctuVPh80Q6pw/rSZj9s4Gex9GxWtIicO1pc8DB9KZWudw==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + dependencies: + npm-normalize-package-bin: 2.0.0 + dev: true + + /npm-normalize-package-bin@2.0.0: + resolution: {integrity: sha512-awzfKUO7v0FscrSpRoogyNm0sajikhBWpU0QMrW09AMi9n1PoKU6WaIqUzuJSQnpciZZmJ/jMZ2Egfmb/9LiWQ==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + dev: true + + /npm-packlist@5.1.3: + resolution: {integrity: sha512-263/0NGrn32YFYi4J533qzrQ/krmmrWwhKkzwTuM4f/07ug51odoaNjUexxO4vxlzURHcmYMH1QjvHjsNDKLVg==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + hasBin: true + dependencies: + glob: 8.1.0 + ignore-walk: 5.0.1 + npm-bundled: 2.0.1 + npm-normalize-package-bin: 2.0.0 + dev: true + + /npm-run-path@5.2.0: + resolution: {integrity: sha512-W4/tgAXFqFA0iL7fk0+uQ3g7wkL8xJmx3XdK0VGb4cHW//eZTtKGvFBBoRKVTpY7n6ze4NL9ly7rgXcHufqXKg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + path-key: 4.0.0 + dev: true + + /once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + dependencies: + wrappy: 1.0.2 + dev: true + + /onetime@6.0.0: + resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} + engines: {node: '>=12'} + dependencies: + mimic-fn: 4.0.0 + dev: true + + /optionator@0.9.3: + resolution: {integrity: sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==} + engines: {node: '>= 0.8.0'} + dependencies: + '@aashutoshrathi/word-wrap': 1.2.6 + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + dev: true + + /p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + dependencies: + yocto-queue: 0.1.0 + dev: true + + /p-limit@5.0.0: + resolution: {integrity: sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==} + engines: {node: '>=18'} + dependencies: + yocto-queue: 1.0.0 + dev: true + + /p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + dependencies: + p-limit: 3.1.0 + dev: true + + /parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + dependencies: + callsites: 3.1.0 + dev: true + + /pascal-case@3.1.2: + resolution: {integrity: sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==} + dependencies: + no-case: 3.0.4 + tslib: 2.6.2 + dev: true + + /path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + dev: true + + /path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + dev: true + + /path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + dev: true + + /path-key@4.0.0: + resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} + engines: {node: '>=12'} + dev: true + + /path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + dev: true + + /pathe@1.1.1: + resolution: {integrity: sha512-d+RQGp0MAYTIaDBIMmOfMwz3E+LOZnxx1HZd5R18mmCZY0QBlK0LDZfPc8FW8Ed2DlvsuE6PRjroDY+wg4+j/Q==} + dev: true + + /pathval@1.1.1: + resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} + dev: true + + /periscopic@3.1.0: + resolution: {integrity: sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==} + dependencies: + '@types/estree': 1.0.5 + estree-walker: 3.0.3 + is-reference: 3.0.2 + dev: true + + /picocolors@1.0.0: + resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} + dev: true + + /picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + dev: true + + /pkg-types@1.0.3: + resolution: {integrity: sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==} + dependencies: + jsonc-parser: 3.2.0 + mlly: 1.4.2 + pathe: 1.1.1 + dev: true + + /postcss-load-config@3.1.4(postcss@8.4.33): + resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==} + engines: {node: '>= 10'} + peerDependencies: + postcss: '>=8.0.9' + ts-node: '>=9.0.0' + peerDependenciesMeta: + postcss: + optional: true + ts-node: + optional: true + dependencies: + lilconfig: 2.1.0 + postcss: 8.4.33 + yaml: 1.10.2 + dev: true + + /postcss-safe-parser@6.0.0(postcss@8.4.33): + resolution: {integrity: sha512-FARHN8pwH+WiS2OPCxJI8FuRJpTVnn6ZNFiqAM2aeW2LwTHWWmWgIyKC6cUo0L8aeKiF/14MNvnpls6R2PBeMQ==} + engines: {node: '>=12.0'} + peerDependencies: + postcss: ^8.3.3 + dependencies: + postcss: 8.4.33 + dev: true + + /postcss-scss@4.0.9(postcss@8.4.33): + resolution: {integrity: sha512-AjKOeiwAitL/MXxQW2DliT28EKukvvbEWx3LBmJIRN8KfBGZbRTxNYW0kSqi1COiTZ57nZ9NW06S6ux//N1c9A==} + engines: {node: '>=12.0'} + peerDependencies: + postcss: ^8.4.29 + dependencies: + postcss: 8.4.33 + dev: true + + /postcss-selector-parser@6.0.15: + resolution: {integrity: sha512-rEYkQOMUCEMhsKbK66tbEU9QVIxbhN18YiniAwA7XQYTVBqrBy+P2p5JcdqsHgKM2zWylp8d7J6eszocfds5Sw==} + engines: {node: '>=4'} + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + dev: true + + /postcss@8.4.33: + resolution: {integrity: sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg==} + engines: {node: ^10 || ^12 || >=14} + dependencies: + nanoid: 3.3.7 + picocolors: 1.0.0 + source-map-js: 1.0.2 + dev: true + + /prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + dev: true + + /prettier-plugin-svelte@3.1.2(prettier@3.1.1)(svelte@4.2.8): + resolution: {integrity: sha512-7xfMZtwgAWHMT0iZc8jN4o65zgbAQ3+O32V6W7pXrqNvKnHnkoyQCGCbKeUyXKZLbYE0YhFRnamfxfkEGxm8qA==} + peerDependencies: + prettier: ^3.0.0 + svelte: ^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0 + dependencies: + prettier: 3.1.1 + svelte: 4.2.8 + dev: true + + /prettier@3.1.1: + resolution: {integrity: sha512-22UbSzg8luF4UuZtzgiUOfcGM8s4tjBv6dJRT7j275NXsy2jb4aJa4NNveul5x4eqlF1wuhuR2RElK71RvmVaw==} + engines: {node: '>=14'} + hasBin: true + dev: true + + /pretty-format@29.7.0: + resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/schemas': 29.6.3 + ansi-styles: 5.2.0 + react-is: 18.2.0 + dev: true + + /publint@0.1.16: + resolution: {integrity: sha512-wJgk7HnXDT5Ap0DjFYbGz78kPkN44iQvDiaq8P63IEEyNU9mYXvaMd2cAyIM6OgqXM/IA3CK6XWIsRq+wjNpgw==} + engines: {node: '>=16'} + hasBin: true + dependencies: + npm-packlist: 5.1.3 + picocolors: 1.0.0 + sade: 1.8.1 + dev: true + + /punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + dev: true + + /queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + dev: true + + /react-is@18.2.0: + resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==} + dev: true + + /readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + dependencies: + picomatch: 2.3.1 + dev: true + + /resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + dev: true + + /reusify@1.0.4: + resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + dev: true + + /rimraf@2.7.1: + resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==} + hasBin: true + dependencies: + glob: 7.2.3 + dev: true + + /rimraf@3.0.2: + resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + hasBin: true + dependencies: + glob: 7.2.3 + dev: true + + /rollup@4.9.3: + resolution: {integrity: sha512-JnchF0ZGFiqGpAPjg3e89j656Ne4tTtCY1VZc1AxtoQcRIxjTu9jyYHBAtkDXE+X681n4un/nX9SU52AroSRzg==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + dependencies: + '@types/estree': 1.0.5 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.9.3 + '@rollup/rollup-android-arm64': 4.9.3 + '@rollup/rollup-darwin-arm64': 4.9.3 + '@rollup/rollup-darwin-x64': 4.9.3 + '@rollup/rollup-linux-arm-gnueabihf': 4.9.3 + '@rollup/rollup-linux-arm64-gnu': 4.9.3 + '@rollup/rollup-linux-arm64-musl': 4.9.3 + '@rollup/rollup-linux-riscv64-gnu': 4.9.3 + '@rollup/rollup-linux-x64-gnu': 4.9.3 + '@rollup/rollup-linux-x64-musl': 4.9.3 + '@rollup/rollup-win32-arm64-msvc': 4.9.3 + '@rollup/rollup-win32-ia32-msvc': 4.9.3 + '@rollup/rollup-win32-x64-msvc': 4.9.3 + fsevents: 2.3.3 + dev: true + + /run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + dependencies: + queue-microtask: 1.2.3 + dev: true + + /sade@1.8.1: + resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==} + engines: {node: '>=6'} + dependencies: + mri: 1.2.0 + dev: true + + /sander@0.5.1: + resolution: {integrity: sha512-3lVqBir7WuKDHGrKRDn/1Ye3kwpXaDOMsiRP1wd6wpZW56gJhsbp5RqQpA6JG/P+pkXizygnr1dKR8vzWaVsfA==} + dependencies: + es6-promise: 3.3.1 + graceful-fs: 4.2.11 + mkdirp: 0.5.6 + rimraf: 2.7.1 + dev: true + + /semver@7.5.4: + resolution: {integrity: sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==} + engines: {node: '>=10'} + hasBin: true + dependencies: + lru-cache: 6.0.0 + dev: true + + /set-cookie-parser@2.6.0: + resolution: {integrity: sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ==} + dev: true + + /shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + dependencies: + shebang-regex: 3.0.0 + dev: true + + /shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + dev: true + + /siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + dev: true + + /signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + dev: true + + /sirv@2.0.4: + resolution: {integrity: sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==} + engines: {node: '>= 10'} + dependencies: + '@polka/url': 1.0.0-next.24 + mrmime: 2.0.0 + totalist: 3.0.1 + dev: true + + /slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + dev: true + + /sorcery@0.11.0: + resolution: {integrity: sha512-J69LQ22xrQB1cIFJhPfgtLuI6BpWRiWu1Y3vSsIwK/eAScqJxd/+CJlUuHQRdX2C9NGFamq+KqNywGgaThwfHw==} + hasBin: true + dependencies: + '@jridgewell/sourcemap-codec': 1.4.15 + buffer-crc32: 0.2.13 + minimist: 1.2.8 + sander: 0.5.1 + dev: true + + /source-map-js@1.0.2: + resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} + engines: {node: '>=0.10.0'} + dev: true + + /stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + dev: true + + /std-env@3.7.0: + resolution: {integrity: sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==} + dev: true + + /strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + dependencies: + ansi-regex: 5.0.1 + dev: true + + /strip-final-newline@3.0.0: + resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} + engines: {node: '>=12'} + dev: true + + /strip-indent@3.0.0: + resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} + engines: {node: '>=8'} + dependencies: + min-indent: 1.0.1 + dev: true + + /strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + dev: true + + /strip-literal@1.3.0: + resolution: {integrity: sha512-PugKzOsyXpArk0yWmUwqOZecSO0GH0bPoctLcqNDH9J04pVW3lflYE0ujElBGTloevcxF5MofAOZ7C5l2b+wLg==} + dependencies: + acorn: 8.11.3 + dev: true + + /supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + dependencies: + has-flag: 4.0.0 + dev: true + + /svelte-check@3.6.2(postcss@8.4.33)(svelte@4.2.8): + resolution: {integrity: sha512-E6iFh4aUCGJLRz6QZXH3gcN/VFfkzwtruWSRmlKrLWQTiO6VzLsivR6q02WYLGNAGecV3EocqZuCDrC2uttZ0g==} + hasBin: true + peerDependencies: + svelte: ^3.55.0 || ^4.0.0-next.0 || ^4.0.0 || ^5.0.0-next.0 + dependencies: + '@jridgewell/trace-mapping': 0.3.20 + chokidar: 3.5.3 + fast-glob: 3.3.2 + import-fresh: 3.3.0 + picocolors: 1.0.0 + sade: 1.8.1 + svelte: 4.2.8 + svelte-preprocess: 5.1.3(postcss@8.4.33)(svelte@4.2.8)(typescript@5.3.3) + typescript: 5.3.3 + transitivePeerDependencies: + - '@babel/core' + - coffeescript + - less + - postcss + - postcss-load-config + - pug + - sass + - stylus + - sugarss + dev: true + + /svelte-eslint-parser@0.33.1(svelte@4.2.8): + resolution: {integrity: sha512-vo7xPGTlKBGdLH8T5L64FipvTrqv3OQRx9d2z5X05KKZDlF4rQk8KViZO4flKERY+5BiVdOh7zZ7JGJWo5P0uA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + svelte: ^3.37.0 || ^4.0.0 + peerDependenciesMeta: + svelte: + optional: true + dependencies: + eslint-scope: 7.2.2 + eslint-visitor-keys: 3.4.3 + espree: 9.6.1 + postcss: 8.4.33 + postcss-scss: 4.0.9(postcss@8.4.33) + svelte: 4.2.8 + dev: true + + /svelte-hmr@0.15.3(svelte@4.2.8): + resolution: {integrity: sha512-41snaPswvSf8TJUhlkoJBekRrABDXDMdpNpT2tfHIv4JuhgvHqLMhEPGtaQn0BmbNSTkuz2Ed20DF2eHw0SmBQ==} + engines: {node: ^12.20 || ^14.13.1 || >= 16} + peerDependencies: + svelte: ^3.19.0 || ^4.0.0 + dependencies: + svelte: 4.2.8 + dev: true + + /svelte-preprocess@5.1.3(postcss@8.4.33)(svelte@4.2.8)(typescript@5.3.3): + resolution: {integrity: sha512-xxAkmxGHT+J/GourS5mVJeOXZzne1FR5ljeOUAMXUkfEhkLEllRreXpbl3dIYJlcJRfL1LO1uIAPpBpBfiqGPw==} + engines: {node: '>= 16.0.0', pnpm: ^8.0.0} + requiresBuild: true + peerDependencies: + '@babel/core': ^7.10.2 + coffeescript: ^2.5.1 + less: ^3.11.3 || ^4.0.0 + postcss: ^7 || ^8 + postcss-load-config: ^2.1.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 + pug: ^3.0.0 + sass: ^1.26.8 + stylus: ^0.55.0 + sugarss: ^2.0.0 || ^3.0.0 || ^4.0.0 + svelte: ^3.23.0 || ^4.0.0-next.0 || ^4.0.0 || ^5.0.0-next.0 + typescript: '>=3.9.5 || ^4.0.0 || ^5.0.0' + peerDependenciesMeta: + '@babel/core': + optional: true + coffeescript: + optional: true + less: + optional: true + postcss: + optional: true + postcss-load-config: + optional: true + pug: + optional: true + sass: + optional: true + stylus: + optional: true + sugarss: + optional: true + typescript: + optional: true + dependencies: + '@types/pug': 2.0.10 + detect-indent: 6.1.0 + magic-string: 0.30.5 + postcss: 8.4.33 + sorcery: 0.11.0 + strip-indent: 3.0.0 + svelte: 4.2.8 + typescript: 5.3.3 + dev: true + + /svelte2tsx@0.6.27(svelte@4.2.8)(typescript@5.3.3): + resolution: {integrity: sha512-E1uPW1o6VsbRz+nUk3fznZ2lSmCITAJoNu8AYefWSvIwE2pSB01i5sId4RMbWNzfcwCQl1DcgGShCPcldl4rvg==} + peerDependencies: + svelte: ^3.55 || ^4.0.0-next.0 || ^4.0 || ^5.0.0-next.0 + typescript: ^4.9.4 || ^5.0.0 + dependencies: + dedent-js: 1.0.1 + pascal-case: 3.1.2 + svelte: 4.2.8 + typescript: 5.3.3 + dev: true + + /svelte@4.2.8: + resolution: {integrity: sha512-hU6dh1MPl8gh6klQZwK/n73GiAHiR95IkFsesLPbMeEZi36ydaXL/ZAb4g9sayT0MXzpxyZjR28yderJHxcmYA==} + engines: {node: '>=16'} + dependencies: + '@ampproject/remapping': 2.2.1 + '@jridgewell/sourcemap-codec': 1.4.15 + '@jridgewell/trace-mapping': 0.3.20 + acorn: 8.11.3 + aria-query: 5.3.0 + axobject-query: 3.2.1 + code-red: 1.0.4 + css-tree: 2.3.1 + estree-walker: 3.0.3 + is-reference: 3.0.2 + locate-character: 3.0.0 + magic-string: 0.30.5 + periscopic: 3.1.0 + dev: true + + /text-table@0.2.0: + resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} + dev: true + + /tiny-glob@0.2.9: + resolution: {integrity: sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg==} + dependencies: + globalyzer: 0.1.0 + globrex: 0.1.2 + dev: true + + /tinybench@2.5.1: + resolution: {integrity: sha512-65NKvSuAVDP/n4CqH+a9w2kTlLReS9vhsAP06MWx+/89nMinJyB2icyl58RIcqCmIggpojIGeuJGhjU1aGMBSg==} + dev: true + + /tinypool@0.8.1: + resolution: {integrity: sha512-zBTCK0cCgRROxvs9c0CGK838sPkeokNGdQVUUwHAbynHFlmyJYj825f/oRs528HaIJ97lo0pLIlDUzwN+IorWg==} + engines: {node: '>=14.0.0'} + dev: true + + /tinyspy@2.2.0: + resolution: {integrity: sha512-d2eda04AN/cPOR89F7Xv5bK/jrQEhmcLFe6HFldoeO9AJtps+fqEnh486vnT/8y4bw38pSyxDcTCAq+Ks2aJTg==} + engines: {node: '>=14.0.0'} + dev: true + + /to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + dependencies: + is-number: 7.0.0 + dev: true + + /totalist@3.0.1: + resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} + engines: {node: '>=6'} + dev: true + + /ts-api-utils@1.0.3(typescript@5.3.3): + resolution: {integrity: sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==} + engines: {node: '>=16.13.0'} + peerDependencies: + typescript: '>=4.2.0' + dependencies: + typescript: 5.3.3 + dev: true + + /tslib@2.6.2: + resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} + dev: true + + /type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + dependencies: + prelude-ls: 1.2.1 + dev: true + + /type-detect@4.0.8: + resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} + engines: {node: '>=4'} + dev: true + + /type-fest@0.20.2: + resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} + engines: {node: '>=10'} + dev: true + + /typescript@5.3.3: + resolution: {integrity: sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==} + engines: {node: '>=14.17'} + hasBin: true + dev: true + + /ufo@1.3.2: + resolution: {integrity: sha512-o+ORpgGwaYQXgqGDwd+hkS4PuZ3QnmqMMxRuajK/a38L6fTpcE5GPIfrf+L/KemFzfUpeUQc1rRS1iDBozvnFA==} + dev: true + + /undici-types@5.26.5: + resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + dev: true + + /uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + dependencies: + punycode: 2.3.1 + dev: true + + /user-agent-data-types@0.4.2: + resolution: {integrity: sha512-jXep3kO/dGNmDOkbDa8ccp4QArgxR4I76m3QVcJ1aOF0B9toc+YtSXtX5gLdDTZXyWlpQYQrABr6L1L2GZOghw==} + dev: true + + /util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + dev: true + + /vite-node@1.1.3(@types/node@20.10.6): + resolution: {integrity: sha512-BLSO72YAkIUuNrOx+8uznYICJfTEbvBAmWClY3hpath5+h1mbPS5OMn42lrTxXuyCazVyZoDkSRnju78GiVCqA==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + dependencies: + cac: 6.7.14 + debug: 4.3.4 + pathe: 1.1.1 + picocolors: 1.0.0 + vite: 5.0.11(@types/node@20.10.6) + transitivePeerDependencies: + - '@types/node' + - less + - lightningcss + - sass + - stylus + - sugarss + - supports-color + - terser + dev: true + + /vite@5.0.11(@types/node@20.10.6): + resolution: {integrity: sha512-XBMnDjZcNAw/G1gEiskiM1v6yzM4GE5aMGvhWTlHAYYhxb7S3/V1s3m2LDHa8Vh6yIWYYB0iJwsEaS523c4oYA==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || >=20.0.0 + less: '*' + lightningcss: ^1.21.0 + sass: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + dependencies: + '@types/node': 20.10.6 + esbuild: 0.19.11 + postcss: 8.4.33 + rollup: 4.9.3 + optionalDependencies: + fsevents: 2.3.3 + dev: true + + /vitefu@0.2.5(vite@5.0.11): + resolution: {integrity: sha512-SgHtMLoqaeeGnd2evZ849ZbACbnwQCIwRH57t18FxcXoZop0uQu0uzlIhJBlF/eWVzuce0sHeqPcDo+evVcg8Q==} + peerDependencies: + vite: ^3.0.0 || ^4.0.0 || ^5.0.0 + peerDependenciesMeta: + vite: + optional: true + dependencies: + vite: 5.0.11(@types/node@20.10.6) + dev: true + + /vitest@1.1.3(@types/node@20.10.6): + resolution: {integrity: sha512-2l8om1NOkiA90/Y207PsEvJLYygddsOyr81wLQ20Ra8IlLKbyQncWsGZjnbkyG2KwwuTXLQjEPOJuxGMG8qJBQ==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/node': ^18.0.0 || >=20.0.0 + '@vitest/browser': ^1.0.0 + '@vitest/ui': ^1.0.0 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + dependencies: + '@types/node': 20.10.6 + '@vitest/expect': 1.1.3 + '@vitest/runner': 1.1.3 + '@vitest/snapshot': 1.1.3 + '@vitest/spy': 1.1.3 + '@vitest/utils': 1.1.3 + acorn-walk: 8.3.1 + cac: 6.7.14 + chai: 4.4.0 + debug: 4.3.4 + execa: 8.0.1 + local-pkg: 0.5.0 + magic-string: 0.30.5 + pathe: 1.1.1 + picocolors: 1.0.0 + std-env: 3.7.0 + strip-literal: 1.3.0 + tinybench: 2.5.1 + tinypool: 0.8.1 + vite: 5.0.11(@types/node@20.10.6) + vite-node: 1.1.3(@types/node@20.10.6) + why-is-node-running: 2.2.2 + transitivePeerDependencies: + - less + - lightningcss + - sass + - stylus + - sugarss + - supports-color + - terser + dev: true + + /which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + dependencies: + isexe: 2.0.0 + dev: true + + /why-is-node-running@2.2.2: + resolution: {integrity: sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==} + engines: {node: '>=8'} + hasBin: true + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + dev: true + + /wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + dev: true + + /yallist@4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + dev: true + + /yaml@1.10.2: + resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} + engines: {node: '>= 6'} + dev: true + + /yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + dev: true + + /yocto-queue@1.0.0: + resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==} + engines: {node: '>=12.20'} + dev: true diff --git a/src/app.d.ts b/src/app.d.ts new file mode 100644 index 0000000..743f07b --- /dev/null +++ b/src/app.d.ts @@ -0,0 +1,13 @@ +// See https://kit.svelte.dev/docs/types#app +// for information about these interfaces +declare global { + namespace App { + // interface Error {} + // interface Locals {} + // interface PageData {} + // interface PageState {} + // interface Platform {} + } +} + +export {}; diff --git a/src/app.html b/src/app.html new file mode 100644 index 0000000..f22aeaa --- /dev/null +++ b/src/app.html @@ -0,0 +1,12 @@ + + + + + + + %sveltekit.head% + + +
%sveltekit.body%
+ + diff --git a/src/index.test.ts b/src/index.test.ts new file mode 100644 index 0000000..e07cbbd --- /dev/null +++ b/src/index.test.ts @@ -0,0 +1,7 @@ +import { describe, it, expect } from 'vitest'; + +describe('sum test', () => { + it('adds 1 + 2 to equal 3', () => { + expect(1 + 2).toBe(3); + }); +}); diff --git a/src/lib/index.ts b/src/lib/index.ts new file mode 100644 index 0000000..7654b76 --- /dev/null +++ b/src/lib/index.ts @@ -0,0 +1,4 @@ +import { initPress, type PressConfig } from './press.js'; +import type { PressEvent } from './types/events.js'; + +export { initPress, type PressEvent, type PressConfig }; diff --git a/src/lib/press.ts b/src/lib/press.ts new file mode 100644 index 0000000..cedcffd --- /dev/null +++ b/src/lib/press.ts @@ -0,0 +1,1017 @@ +import { disableTextSelection, restoreTextSelection } from './utils/text-selection.js'; +import type { FocusableElement } from './types/dom.js'; +import type { PressEvent as IPressEvent, PointerType, PressEvents } from './types/events.js'; +import { focusWithoutScrolling } from './utils/focus-without-scroll.js'; +import { getOwnerDocument, getOwnerWindow } from './utils/get-owner.js'; +import { isMac } from './utils/platform.js'; +import { isVirtualClick, isVirtualPointerEvent } from './utils/is-virtual-event.js'; +import { openLink } from './utils/open-link.js'; + +import { createGlobalListeners } from './utils/global-listeners.js'; +import { get, writable, type Writable } from 'svelte/store'; +import { toWritableStores } from './utils/to-writable-stores.js'; +import { executeCallbacks, noop } from './utils/callbacks.js'; +import { addEventListener } from './utils/event-listeners.js'; +import type { ActionReturn } from 'svelte/action'; + +export type PressConfig = PressEvents & { + /** Whether the target is in a controlled press state (e.g. an overlay it triggers is open). */ + isPressed?: boolean; + /** Whether the press events should be disabled. */ + isDisabled?: boolean; + /** Whether the target should not receive focus on press. */ + preventFocusOnPress?: boolean; + /** + * Whether press events should be canceled when the pointer leaves the target while pressed. + * By default, this is `false`, which means if the pointer returns back over the target while + * still pressed, onPressStart will be fired again. If set to `true`, the press is canceled + * when the pointer leaves the target and onPressStart will not be fired if the pointer returns. + */ + shouldCancelOnPointerExit?: boolean; + /** Whether text selection should be enabled on the pressable element. */ + allowTextSelectionOnPress?: boolean; +}; + +type PressState = { + isPressed: boolean; + ignoreEmulatedMouseEvents: boolean; + ignoreClickAfterPress: boolean; + didFirePressStart: boolean; + isTriggeringEvent: boolean; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + activePointerId: any; + target: FocusableElement | null; + isOverTarget: boolean; + pointerType: PointerType | null; + userSelect?: string; + metaKeyEvents?: Map; +}; + +type EventBase = { + currentTarget: EventTarget | null; + shiftKey: boolean; + ctrlKey: boolean; + metaKey: boolean; + altKey: boolean; +}; + +type PressActionReturn = ActionReturn< + undefined, + { + 'on:press'?: (e: CustomEvent) => void; + 'on:pressstart'?: (e: CustomEvent) => void; + 'on:pressend'?: (e: CustomEvent) => void; + 'on:pressup'?: (e: CustomEvent) => void; + } +>; + +export type PressResult = { + /** Whether the target is currently pressed. */ + isPressed: Writable; + /** A Svelte Action which handles applying the event listeners to the element. */ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + pressAction: (node: HTMLElement | SVGElement) => PressActionReturn; +}; + +class PressEvent implements IPressEvent { + type: IPressEvent['type']; + pointerType: PointerType; + target: Element; + shiftKey: boolean; + ctrlKey: boolean; + metaKey: boolean; + altKey: boolean; + #shouldStopPropagation = true; + + constructor(type: IPressEvent['type'], pointerType: PointerType, originalEvent: EventBase) { + this.type = type; + this.pointerType = pointerType; + this.target = originalEvent.currentTarget as Element; + this.shiftKey = originalEvent.shiftKey; + this.metaKey = originalEvent.metaKey; + this.ctrlKey = originalEvent.ctrlKey; + this.altKey = originalEvent.altKey; + } + + continuePropagation() { + this.#shouldStopPropagation = false; + } + + get shouldStopPropagation() { + return this.#shouldStopPropagation; + } +} + +const LINK_CLICKED = Symbol('linkClicked'); + +/** + * Handles press interactions across mouse, touch, keyboard, and screen readers. + * It normalizes behavior across browsers and platforms, and handles many nuances + * of dealing with pointer and keyboard events. + */ +export function initPress(config?: PressConfig): PressResult { + const defaults = { + isPressed: false, + isDisabled: false, + preventFocusOnPress: false, + shouldCancelOnPointerExit: false, + allowTextSelectionOnPress: false + }; + const { + onPress, + onPressChange, + onPressStart, + onPressEnd, + onPressUp, + isPressed: isPressedProp, + ...rest + } = { ...defaults, ...config }; + + const opts = toWritableStores({ ...rest, isPressedProp }); + + const isPressed = writable(false); + + const pressState = writable({ + isPressed: false, + ignoreEmulatedMouseEvents: false, + ignoreClickAfterPress: false, + didFirePressStart: false, + isTriggeringEvent: false, + activePointerId: null, + target: null, + isOverTarget: false, + pointerType: null + }); + + const { addGlobalListener, removeAllGlobalListeners } = createGlobalListeners(); + + let nodeEl: HTMLElement | SVGElement | null = null; + + function triggerPressStart(originalEvent: EventBase, pointerType: PointerType) { + const $state = get(pressState); + const $isDisabled = get(opts.isDisabled); + + if ($isDisabled || $state.didFirePressStart) { + return false; + } + let shouldStopPropagation = true; + pressState.update((curr) => ({ ...curr, isTriggeringEvent: true })); + + const event = new PressEvent('pressstart', pointerType, originalEvent); + onPressStart?.(event); + nodeEl?.dispatchEvent(new CustomEvent('pressstart', { detail: event })); + shouldStopPropagation = event.shouldStopPropagation; + + onPressChange?.(true); + + pressState.update((curr) => ({ + ...curr, + isTriggeringEvent: false, + didFirePressStart: true + })); + isPressed.set(true); + return shouldStopPropagation; + } + + function triggerPressEnd(originalEvent: EventBase, pointerType: PointerType, wasPressed = true) { + const $state = get(pressState); + if (!$state.didFirePressStart) { + return false; + } + + pressState.update((curr) => ({ + ...curr, + ignoreClickAfterPress: true, + didFirePressStart: false, + isTriggeringEvent: true + })); + + let shouldStopPropagation = true; + + const event = new PressEvent('pressend', pointerType, originalEvent); + onPressEnd?.(event); + nodeEl?.dispatchEvent(new CustomEvent('pressend', { detail: event })); + shouldStopPropagation = event.shouldStopPropagation; + + onPressChange?.(false); + + isPressed.set(false); + const $isDisabled = get(opts.isDisabled); + if (wasPressed && !$isDisabled) { + const event = new PressEvent('press', pointerType, originalEvent); + onPress?.(event); + nodeEl?.dispatchEvent(new CustomEvent('press', { detail: event })); + shouldStopPropagation &&= event.shouldStopPropagation; + } + + pressState.update((curr) => ({ + ...curr, + isTriggeringEvent: false + })); + + return shouldStopPropagation; + } + + function triggerPressUp(originalEvent: EventBase, pointerType: PointerType) { + if (get(opts.isDisabled)) { + return false; + } + + pressState.update((curr) => ({ ...curr, isTriggeringEvent: true })); + const event = new PressEvent('pressup', pointerType, originalEvent); + onPressUp?.(event); + nodeEl?.dispatchEvent(new CustomEvent('pressup', { detail: event })); + pressState.update((curr) => ({ ...curr, isTriggeringEvent: false })); + return event.shouldStopPropagation; + } + + function cancel(e: EventBase) { + const $state = get(pressState); + if ($state.isPressed && $state.target) { + if ($state.isOverTarget && $state.pointerType != null) { + triggerPressEnd(createEvent($state.target, e), $state.pointerType, false); + } + pressState.update((curr) => ({ + ...curr, + isPressed: false, + isOverTarget: false, + activePointerId: null, + pointerType: null + })); + removeAllGlobalListeners(); + if (!get(opts.allowTextSelectionOnPress)) { + restoreTextSelection($state.target); + } + } + } + + function cancelOnPointerExit(e: EventBase) { + if (get(opts.shouldCancelOnPointerExit)) { + cancel(e); + } + } + + function onKeyUp(e: KeyboardEvent) { + const $state = get(pressState); + if ($state.isPressed && $state.target && isValidKeyboardEvent(e, $state.target)) { + if (shouldPreventDefaultKeyboard(e.target as Element, e.key)) { + e.preventDefault(); + } + + const target = e.target as Element; + const shouldStopPropagation = triggerPressEnd( + createEvent($state.target, e), + 'keyboard', + $state.target.contains(target) + ); + removeAllGlobalListeners(); + + if (shouldStopPropagation) { + e.stopPropagation(); + } + + // If a link was triggered with a key other than Enter, open the URL ourselves. + // This means the link has a role override, and the default browser behavior + // only applies when using the Enter key. + if ( + e.key !== 'Enter' && + isHTMLAnchorLink($state.target) && + $state.target.contains(target) && + //@ts-expect-error - applyig a symbol to an event + !e[LINK_CLICKED] + ) { + // Store a hidden property on the event so we only trigger link click once, + // even if there are multiple usePress instances attached to the element. + //@ts-expect-error - applyig a symbol to an event + e[LINK_CLICKED] = true; + openLink($state.target, e, false); + } + $state.metaKeyEvents?.delete(e.key); + pressState.update((curr) => ({ + ...curr, + isPressed: false, + metaKeyEvents: $state.metaKeyEvents + })); + } else if (e.key === 'Meta' && $state.metaKeyEvents?.size) { + // If we recorded keydown events that occurred while the Meta key was pressed, + // and those haven't received keyup events already, fire keyup events ourselves. + // See comment above for more info about the macOS bug causing this. + const events = $state.metaKeyEvents; + pressState.update((curr) => ({ ...curr, metaKeyEvents: undefined })); + for (const event of events.values()) { + $state.target?.dispatchEvent(new KeyboardEvent('keyup', event)); + } + } + } + + function getBaseHandlers() { + return { + onKeyDown: (e: KeyboardEvent) => { + const currentTarget = e.currentTarget; + if (!isHTMLorSVGElement(currentTarget)) return; + if (isValidKeyboardEvent(e, currentTarget) && currentTarget.contains(e.target as Element)) { + if (shouldPreventDefaultKeyboard(e.target as Element, e.key)) { + e.preventDefault(); + } + const $state = get(pressState); + + // If the event is repeating, it may have started on a different element + // after which focus moved to the current element. Ignore these events and + // only handle the first key down event. + let shouldStopPropagation = true; + if (!$state.isPressed && !e.repeat) { + pressState.update((curr) => ({ ...curr, target: currentTarget, isPressed: true })); + shouldStopPropagation = triggerPressStart(e, 'keyboard'); + + // Focus may move before the key up event, so register the event on the document + // instead of the same element where the key down event occurred. + addGlobalListener(getOwnerDocument(currentTarget), 'keyup', onKeyUp, false); + } + + if (shouldStopPropagation) { + e.stopPropagation(); + } + + // Keep track of the keydown events that occur while the Meta (e.g. Command) key is held. + // macOS has a bug where keyup events are not fired while the Meta key is down. + // When the Meta key itself is released we will get an event for that, and we'll act as if + // all of these other keys were released as well. + // https://bugs.chromium.org/p/chromium/issues/detail?id=1393524 + // https://bugs.webkit.org/show_bug.cgi?id=55291 + // https://bugzilla.mozilla.org/show_bug.cgi?id=1299553 + if (e.metaKey && isMac()) { + $state.metaKeyEvents?.set(e.key, e); + pressState.update((curr) => ({ ...curr, metaKeyEvents: $state.metaKeyEvents })); + } + } else if (e.key === 'Meta') { + pressState.update((curr) => ({ ...curr, metaKeyEvents: new Map() })); + } + }, + onKeyUp: (e: KeyboardEvent) => { + const currentTarget = e.currentTarget; + if (!isHTMLorSVGElement(currentTarget)) return; + const $state = get(pressState); + if ( + isValidKeyboardEvent(e, currentTarget) && + !e.repeat && + currentTarget.contains(e.target as Element) && + $state.target + ) { + triggerPressUp(createEvent($state.target, e), 'keyboard'); + } + }, + onClick: (e: MouseEvent) => { + const $state = get(pressState); + const currentTarget = e.currentTarget; + if (!isHTMLorSVGElement(currentTarget)) return; + if (!currentTarget.contains(e.target as Element)) return; + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + if (e.button === 0 && !$state.isTriggeringEvent && (openLink as any).isOpening) { + let shouldStopPropagation = true; + const $isDisabled = get(opts.isDisabled); + if ($isDisabled) { + e.preventDefault(); + } + + // If triggered from a screen reader or by using element.click(), + // trigger as if it were a keyboard click. + if ( + !$state.ignoreClickAfterPress && + !$state.ignoreEmulatedMouseEvents && + !$state.isPressed && + ($state.pointerType === 'virtual' || isVirtualClick(e)) + ) { + // Ensure the element receives focus (VoiceOver on iOS does not do this) + if (!$isDisabled && !get(opts.preventFocusOnPress)) { + focusWithoutScrolling(currentTarget); + } + + const stopPressStart = triggerPressStart(e, 'virtual'); + const stopPressUp = triggerPressUp(e, 'virtual'); + const stopPressEnd = triggerPressEnd(e, 'virtual'); + shouldStopPropagation = stopPressStart && stopPressUp && stopPressEnd; + } + + pressState.update((curr) => ({ + ...curr, + ignoreEmulatedMouseEvents: false, + ignoreClickAfterPress: false + })); + + if (shouldStopPropagation) { + e.stopPropagation(); + } + } + } + }; + } + + function getPointerHandlers() { + function onPointerUp(e: PointerEvent) { + const $state = get(pressState); + if ( + e.pointerId === $state.activePointerId && + $state.isPressed && + e.button === 0 && + $state.target + ) { + if (isOverTarget(e, $state.target) && $state.pointerType != null) { + triggerPressEnd(createEvent($state.target, e), $state.pointerType); + } else if ($state.isOverTarget && $state.pointerType != null) { + triggerPressEnd(createEvent($state.target, e), $state.pointerType, false); + } + + pressState.update((curr) => ({ + ...curr, + isPressed: false, + isOverTarget: false, + activePointerId: null, + pointerType: null + })); + + removeAllGlobalListeners(); + if (!get(opts.allowTextSelectionOnPress)) { + restoreTextSelection($state.target); + } + } + } + // Safari on iOS < 13.2 does not implement pointerenter/pointerleave events correctly. + // Use pointer move events instead to implement our own hit testing. + // See https://bugs.webkit.org/show_bug.cgi?id=199803 + function onPointerMove(e: PointerEvent) { + const $state = get(pressState); + if (e.pointerId !== $state.activePointerId) { + return; + } + + if ($state.target && isOverTarget(e, $state.target)) { + if (!$state.isOverTarget && $state.pointerType != null) { + pressState.update((curr) => ({ ...curr, isOverTarget: true })); + triggerPressStart(createEvent($state.target, e), $state.pointerType); + } + } else if ($state.target && $state.isOverTarget && $state.pointerType != null) { + pressState.update((curr) => ({ ...curr, isOverTarget: false })); + triggerPressEnd(createEvent($state.target, e), $state.pointerType, false); + cancelOnPointerExit(e); + } + } + + function onPointerCancel(e: PointerEvent) { + cancel(e); + } + + return { + onPointerDown: (e: PointerEvent) => { + const currentTarget = e.currentTarget; + if (!isHTMLorSVGElement(currentTarget)) return; + if (e.button !== 0 || !currentTarget.contains(e.target as HTMLElement)) return; + const $state = get(pressState); + + // iOS safari fires pointer events from VoiceOver with incorrect coordinates/target. + // Ignore and let the onClick handler take care of it instead. + // https://bugs.webkit.org/show_bug.cgi?id=222627 + // https://bugs.webkit.org/show_bug.cgi?id=223202 + if (isVirtualPointerEvent(e)) { + pressState.update((curr) => ({ ...curr, pointerType: 'virtual' })); + return; + } + + pressState.update((curr) => ({ ...curr, pointerType: e.pointerType as PointerType })); + + let shouldStopPropagation = true; + + if (!$state.isPressed) { + pressState.update((curr) => ({ + ...curr, + isPressed: true, + isOverTarget: true, + activePointerId: e.pointerId, + target: currentTarget + })); + + if (!get(opts.isDisabled) && !get(opts.preventFocusOnPress)) { + focusWithoutScrolling(currentTarget); + } + + if (!get(opts.allowTextSelectionOnPress)) { + disableTextSelection(currentTarget); + } + + shouldStopPropagation = triggerPressStart(e, e.pointerType as PointerType); + + addGlobalListener(getOwnerDocument(currentTarget), 'pointermove', onPointerMove, false); + addGlobalListener(getOwnerDocument(currentTarget), 'pointerup', onPointerUp, false); + addGlobalListener( + getOwnerDocument(currentTarget), + 'pointercancel', + onPointerCancel, + false + ); + } + + if (shouldStopPropagation) { + e.stopPropagation(); + } + }, + onMouseDown: (e: MouseEvent) => { + const currentTarget = e.currentTarget; + if (!isHTMLorSVGElement(currentTarget)) return; + if (!currentTarget.contains(e.target as Element)) { + return; + } + + if (e.button === 0) { + // Chrome and Firefox on touch Windows devices require mouse down events + // to be canceled in addition to pointer events, or an extra asynchronous + // focus event will be fired. + if (shouldPreventDefault(e.currentTarget as Element)) { + e.preventDefault(); + } + + e.stopPropagation(); + } + }, + onPointerUp: (e: PointerEvent) => { + const currentTarget = e.currentTarget; + if (!isHTMLorSVGElement(currentTarget)) return; + const $state = get(pressState); + if (!currentTarget.contains(e.target as HTMLElement) || $state.pointerType === 'virtual') + return; + + // Only handle left clicks + // Safari on iOS sometimes fires pointerup events, even + // when the touch isn't over the target, so double check. + if (e.button === 0 && isOverTarget(e, currentTarget)) { + triggerPressUp(e, $state.pointerType || (e.pointerType as PointerType)); + } + } + }; + } + + function getNonPointerHandlers() { + function onMouseUp(e: MouseEvent) { + // Only handle left clicks + if (e.button !== 0) { + return; + } + const $state = get(pressState); + + pressState.update((curr) => ({ ...curr, isPressed: false })); + removeAllGlobalListeners(); + + if ($state.ignoreEmulatedMouseEvents) { + pressState.update((curr) => ({ ...curr, ignoreEmulatedMouseEvents: false })); + return; + } + + if ($state.target && isOverTarget(e, $state.target) && $state.pointerType != null) { + triggerPressEnd(createEvent($state.target, e), $state.pointerType); + } else if ($state.target && $state.isOverTarget && $state.pointerType != null) { + triggerPressEnd(createEvent($state.target, e), $state.pointerType, false); + } + + pressState.update((curr) => ({ ...curr, isOverTarget: false })); + } + + function onScroll(e: Event) { + const $state = get(pressState); + if ($state.isPressed && (e.target as Element).contains($state.target)) { + cancel({ + currentTarget: $state.target, + shiftKey: false, + ctrlKey: false, + metaKey: false, + altKey: false + }); + } + } + + return { + onMouseDown: (e: MouseEvent) => { + const currentTarget = e.currentTarget; + const $state = get(pressState); + if (!isHTMLorSVGElement(currentTarget)) return; + // Only handle left clicks + if (e.button !== 0 || !currentTarget.contains(e.target as Element)) { + return; + } + + // Due to browser inconsistencies, especially on mobile browsers, we prevent + // default on mouse down and handle focusing the pressable element ourselves. + if (shouldPreventDefault(currentTarget)) { + e.preventDefault(); + } + + if ($state.ignoreEmulatedMouseEvents) { + e.stopPropagation(); + return; + } + + $state.pointerType = isVirtualClick(e) ? 'virtual' : 'mouse'; + pressState.update((curr) => ({ + ...curr, + isPressed: true, + isOverTarget: true, + target: currentTarget, + pointerType: $state.pointerType + })); + + if (!get(opts.isDisabled) && !get(opts.preventFocusOnPress)) { + focusWithoutScrolling(currentTarget); + } + + const shouldStopPropagation = triggerPressStart(e, $state.pointerType as PointerType); + if (shouldStopPropagation) { + e.stopPropagation(); + } + + addGlobalListener(getOwnerDocument(currentTarget), 'mouseup', onMouseUp, false); + }, + onMouseEnter: (e: MouseEvent) => { + const currentTarget = e.currentTarget; + if (!isHTMLorSVGElement(currentTarget)) return; + if (!currentTarget.contains(e.target as Element)) { + return; + } + const $state = get(pressState); + + let shouldStopPropagation = true; + if ($state.isPressed && !$state.ignoreEmulatedMouseEvents && $state.pointerType != null) { + pressState.update((curr) => ({ ...curr, isOverTarget: true })); + shouldStopPropagation = triggerPressStart(e, $state.pointerType); + } + + if (shouldStopPropagation) { + e.stopPropagation(); + } + }, + onMouseLeave: (e: MouseEvent) => { + const currentTarget = e.currentTarget; + if (!isHTMLorSVGElement(currentTarget)) return; + if (!currentTarget.contains(e.target as Element)) { + return; + } + + let shouldStopPropagation = true; + const $state = get(pressState); + if ($state.isPressed && !$state.ignoreEmulatedMouseEvents && $state.pointerType != null) { + pressState.update((curr) => ({ ...curr, isOverTarget: false })); + shouldStopPropagation = triggerPressEnd(e, $state.pointerType, false); + cancelOnPointerExit(e); + } + + if (shouldStopPropagation) { + e.stopPropagation(); + } + }, + onMouseUp: (e: MouseEvent) => { + const currentTarget = e.currentTarget; + if (!isHTMLorSVGElement(currentTarget)) return; + if (!currentTarget.contains(e.target as Element)) { + return; + } + const $state = get(pressState); + if (!$state.ignoreEmulatedMouseEvents && e.button === 0) { + triggerPressUp(e, $state.pointerType || 'mouse'); + } + }, + onTouchStart: (e: TouchEvent) => { + const currentTarget = e.currentTarget; + if (!isHTMLorSVGElement(currentTarget)) return; + if (!currentTarget.contains(e.target as Element)) { + return; + } + + const touch = getTouchFromEvent(e); + if (!touch) return; + + pressState.update((curr) => ({ + ...curr, + activePointerId: touch.identifier, + ignoreEmulatedMouseEvents: true, + isOverTarget: true, + isPressed: true, + target: currentTarget, + pointerType: 'touch' + })); + + // Due to browser inconsistencies, especially on mobile browsers, we prevent default + // on the emulated mouse event and handle focusing the pressable element ourselves. + if (!get(opts.isDisabled) && !get(opts.preventFocusOnPress)) { + focusWithoutScrolling(currentTarget); + } + const $state = get(pressState); + + if (!get(opts.allowTextSelectionOnPress)) { + disableTextSelection($state.target as HTMLElement); + } + + const shouldStopPropagation = triggerPressStart(e, $state.pointerType as PointerType); + if (shouldStopPropagation) { + e.stopPropagation(); + } + + addGlobalListener(getOwnerWindow(currentTarget), 'scroll', onScroll, true); + }, + onTouchMove: (e: TouchEvent) => { + const currentTarget = e.currentTarget; + if (!isHTMLorSVGElement(currentTarget)) return; + if (!currentTarget.contains(e.target as Element)) { + return; + } + const $state = get(pressState); + + if (!$state.isPressed) { + e.stopPropagation(); + return; + } + + const touch = getTouchById(e, $state.activePointerId); + let shouldStopPropagation = true; + + if (touch && isOverTarget(touch, currentTarget)) { + if (!$state.isOverTarget && $state.pointerType != null) { + pressState.update((curr) => ({ ...curr, isOverTarget: true })); + shouldStopPropagation = triggerPressStart(e, $state.pointerType); + } + } else if ($state.isOverTarget && $state.pointerType != null) { + pressState.update((curr) => ({ ...curr, isOverTarget: false })); + shouldStopPropagation = triggerPressEnd(e, $state.pointerType, false); + cancelOnPointerExit(e); + } + + if (shouldStopPropagation) { + e.stopPropagation(); + } + }, + onTouchEnd: (e: TouchEvent) => { + const currentTarget = e.currentTarget; + if (!isHTMLorSVGElement(currentTarget)) return; + if (!currentTarget.contains(e.target as Element)) { + return; + } + const $state = get(pressState); + + if (!$state.isPressed) { + e.stopPropagation(); + return; + } + + const touch = getTouchById(e, $state.activePointerId); + let shouldStopPropagation = true; + if (touch && isOverTarget(touch, currentTarget) && $state.pointerType != null) { + triggerPressUp(e, $state.pointerType); + shouldStopPropagation = triggerPressEnd(e, $state.pointerType); + } else if ($state.isOverTarget && $state.pointerType != null) { + shouldStopPropagation = triggerPressEnd(e, $state.pointerType, false); + } + + if (shouldStopPropagation) { + e.stopPropagation(); + } + + pressState.update((curr) => ({ + ...curr, + isPressed: false, + activePointerId: null, + isOverTarget: false, + ignoreEmulatedMouseEvents: true + })); + + if ($state.target && !get(opts.allowTextSelectionOnPress)) { + restoreTextSelection($state.target); + } + removeAllGlobalListeners(); + }, + onTouchCancel: (e: TouchEvent) => { + const currentTarget = e.currentTarget; + if (!isHTMLorSVGElement(currentTarget)) return; + if (!currentTarget.contains(e.target as Element)) { + return; + } + + e.stopPropagation(); + const $state = get(pressState); + if ($state.isPressed) { + cancel(e); + } + }, + onDragStart: (e: DragEvent) => { + const currentTarget = e.currentTarget; + if (!isHTMLorSVGElement(currentTarget)) return; + if (!currentTarget.contains(e.target as Element)) { + return; + } + + cancel(e); + } + }; + } + + function pressAction(node: HTMLElement | SVGElement) { + const baseHandlers = getBaseHandlers(); + nodeEl = node; + + const unsubBaseHandlers = executeCallbacks( + addEventListener(node, 'keydown', baseHandlers.onKeyDown), + addEventListener(node, 'keyup', baseHandlers.onKeyUp), + addEventListener(node, 'click', baseHandlers.onClick) + ); + + let unsubOtherHandlers = noop; + + if (typeof PointerEvent !== 'undefined') { + const handlers = getPointerHandlers(); + unsubOtherHandlers = executeCallbacks( + addEventListener(node, 'pointerdown', handlers.onPointerDown), + addEventListener(node, 'mousedown', handlers.onMouseDown), + addEventListener(node, 'pointerup', handlers.onPointerUp) + ); + } else { + const handlers = getNonPointerHandlers(); + unsubOtherHandlers = executeCallbacks( + addEventListener(node, 'mousedown', handlers.onMouseDown), + addEventListener(node, 'mouseenter', handlers.onMouseEnter), + addEventListener(node, 'mouseleave', handlers.onMouseLeave), + addEventListener(node, 'mouseup', handlers.onMouseUp), + addEventListener(node, 'touchstart', handlers.onTouchStart), + addEventListener(node, 'touchmove', handlers.onTouchMove), + addEventListener(node, 'touchend', handlers.onTouchEnd), + addEventListener(node, 'touchcancel', handlers.onTouchCancel), + addEventListener(node, 'dragstart', handlers.onDragStart) + ); + } + + return { + destroy() { + // Remove user-select: none in case destroy immediately after pressStart + if (!get(opts.allowTextSelectionOnPress)) { + const target = get(pressState).target; + if (target) { + restoreTextSelection(target); + } + } + unsubBaseHandlers(); + unsubOtherHandlers(); + } + }; + } + + return { + pressAction, + isPressed + }; +} + +function isHTMLAnchorLink(target: Element): target is HTMLAnchorElement { + return target.tagName === 'A' && target.hasAttribute('href'); +} + +function isValidKeyboardEvent(event: KeyboardEvent, currentTarget: Element): boolean { + const { key, code } = event; + const element = currentTarget as HTMLElement; + const role = element.getAttribute('role'); + // Accessibility for keyboards. Space and Enter only. + // "Spacebar" is for IE 11 + return ( + (key === 'Enter' || key === ' ' || key === 'Spacebar' || code === 'Space') && + !( + (element instanceof getOwnerWindow(element).HTMLInputElement && + !isValidInputKey(element, key)) || + element instanceof getOwnerWindow(element).HTMLTextAreaElement || + element.isContentEditable + ) && + // Links should only trigger with Enter key + !((role === 'link' || (!role && isHTMLAnchorLink(element))) && key !== 'Enter') + ); +} + +function getTouchFromEvent(event: TouchEvent): Touch | null { + const { targetTouches } = event; + if (targetTouches.length > 0) { + return targetTouches[0]; + } + return null; +} + +function getTouchById(event: TouchEvent, pointerId: null | number): null | Touch { + const changedTouches = event.changedTouches; + for (let i = 0; i < changedTouches.length; i++) { + const touch = changedTouches[i]; + if (touch.identifier === pointerId) { + return touch; + } + } + return null; +} + +function createEvent(target: FocusableElement, e: EventBase): EventBase { + return { + currentTarget: target, + shiftKey: e.shiftKey, + ctrlKey: e.ctrlKey, + metaKey: e.metaKey, + altKey: e.altKey + }; +} + +interface Rect { + top: number; + right: number; + bottom: number; + left: number; +} + +interface EventPoint { + clientX: number; + clientY: number; + width?: number; + height?: number; + radiusX?: number; + radiusY?: number; +} + +function getPointClientRect(point: EventPoint): Rect { + let offsetX = 0; + let offsetY = 0; + if (point.width !== undefined) { + offsetX = point.width / 2; + } else if (point.radiusX !== undefined) { + offsetX = point.radiusX; + } + if (point.height !== undefined) { + offsetY = point.height / 2; + } else if (point.radiusY !== undefined) { + offsetY = point.radiusY; + } + + return { + top: point.clientY - offsetY, + right: point.clientX + offsetX, + bottom: point.clientY + offsetY, + left: point.clientX - offsetX + }; +} + +function areRectanglesOverlapping(a: Rect, b: Rect) { + // check if they cannot overlap on x axis + if (a.left > b.right || b.left > a.right) { + return false; + } + // check if they cannot overlap on y axis + if (a.top > b.bottom || b.top > a.bottom) { + return false; + } + return true; +} + +function isOverTarget(point: EventPoint, target: Element) { + const rect = target.getBoundingClientRect(); + const pointRect = getPointClientRect(point); + return areRectanglesOverlapping(rect, pointRect); +} + +function shouldPreventDefault(target: Element) { + // We cannot prevent default if the target is a draggable element. + return !(target instanceof HTMLElement) || !target.hasAttribute('draggable'); +} + +function shouldPreventDefaultKeyboard(target: Element, key: string) { + if (target instanceof HTMLInputElement) { + return !isValidInputKey(target, key); + } + + if (target instanceof HTMLButtonElement) { + return target.type !== 'submit' && target.type !== 'reset'; + } + + if (isHTMLAnchorLink(target)) { + return false; + } + + return true; +} + +const nonTextInputTypes = new Set([ + 'checkbox', + 'radio', + 'range', + 'color', + 'file', + 'image', + 'button', + 'submit', + 'reset' +]); + +function isValidInputKey(target: HTMLInputElement, key: string) { + // Only space should toggle checkboxes and radios, not enter. + return target.type === 'checkbox' || target.type === 'radio' + ? key === ' ' + : nonTextInputTypes.has(target.type); +} + +function isHTMLorSVGElement(el: unknown): el is HTMLElement | SVGElement { + return el instanceof HTMLElement || el instanceof SVGElement; +} diff --git a/src/lib/types/dom.ts b/src/lib/types/dom.ts new file mode 100644 index 0000000..2cc2d10 --- /dev/null +++ b/src/lib/types/dom.ts @@ -0,0 +1,2 @@ +/** Any focusable element, including both HTML and SVG elements. */ +export type FocusableElement = HTMLElement | SVGElement; diff --git a/src/lib/types/events.ts b/src/lib/types/events.ts new file mode 100644 index 0000000..935ad5f --- /dev/null +++ b/src/lib/types/events.ts @@ -0,0 +1,146 @@ +// Portions of the code in this file are based on code from Adobe. +// Original licensing for the following can be found: +// See https://github.com/adobe/react-spectrum + +export type PointerType = 'mouse' | 'pen' | 'touch' | 'keyboard' | 'virtual'; + +export interface PressEvent { + /** The type of press event being fired. */ + type: 'pressstart' | 'pressend' | 'pressup' | 'press'; + /** The pointer type that triggered the press event. */ + pointerType: PointerType; + /** The target element of the press event. */ + target: Element; + /** Whether the shift keyboard modifier was held during the press event. */ + shiftKey: boolean; + /** Whether the ctrl keyboard modifier was held during the press event. */ + ctrlKey: boolean; + /** Whether the meta keyboard modifier was held during the press event. */ + metaKey: boolean; + /** Whether the alt keyboard modifier was held during the press event. */ + altKey: boolean; + /** + * By default, press events stop propagation to parent elements. + * In cases where a handler decides not to handle a specific event, + * it can call `continuePropagation()` to allow a parent to handle it. + */ + continuePropagation(): void; +} + +export interface LongPressEvent extends Omit { + /** The type of long press event being fired. */ + type: 'longpressstart' | 'longpressend' | 'longpress'; +} + +export interface HoverEvent { + /** The type of hover event being fired. */ + type: 'hoverstart' | 'hoverend'; + /** The pointer type that triggered the hover event. */ + pointerType: 'mouse' | 'pen'; + /** The target element of the hover event. */ + target: HTMLElement; +} + +export interface KeyboardEvents { + /** Handler that is called when a key is pressed. */ + onKeyDown?: (e: KeyboardEvent) => void; + /** Handler that is called when a key is released. */ + onKeyUp?: (e: KeyboardEvent) => void; +} + +export interface FocusEvents { + /** Handler that is called when the element receives focus. */ + onFocus?: (e: FocusEvent) => void; + /** Handler that is called when the element loses focus. */ + onBlur?: (e: FocusEvent) => void; + /** Handler that is called when the element's focus status changes. */ + onFocusChange?: (isFocused: boolean) => void; +} + +export interface HoverEvents { + /** Handler that is called when a hover interaction starts. */ + onHoverStart?: (e: HoverEvent) => void; + /** Handler that is called when a hover interaction ends. */ + onHoverEnd?: (e: HoverEvent) => void; + /** Handler that is called when the hover state changes. */ + onHoverChange?: (isHovering: boolean) => void; +} + +export type PressEvents = { + /** Handler that is called when the press is released over the target. */ + onPress?: (e: PressEvent) => void; + /** Handler that is called when a press interaction starts. */ + onPressStart?: (e: PressEvent) => void; + /** + * Handler that is called when a press interaction ends, either + * over the target or when the pointer leaves the target. + */ + onPressEnd?: (e: PressEvent) => void; + /** Handler that is called when the press state changes. */ + onPressChange?: (isPressed: boolean) => void; + /** + * Handler that is called when a press is released over the target, regardless of + * whether it started on the target or not. + */ + onPressUp?: (e: PressEvent) => void; +}; + +export interface FocusableProps extends FocusEvents, KeyboardEvents { + /** Whether the element should receive focus on render. */ + autoFocus?: boolean; +} + +interface BaseMoveEvent { + /** The pointer type that triggered the move event. */ + pointerType: PointerType; + /** Whether the shift keyboard modifier was held during the move event. */ + shiftKey: boolean; + /** Whether the ctrl keyboard modifier was held during the move event. */ + ctrlKey: boolean; + /** Whether the meta keyboard modifier was held during the move event. */ + metaKey: boolean; + /** Whether the alt keyboard modifier was held during the move event. */ + altKey: boolean; +} + +export interface MoveStartEvent extends BaseMoveEvent { + /** The type of move event being fired. */ + type: 'movestart'; +} + +export interface MoveMoveEvent extends BaseMoveEvent { + /** The type of move event being fired. */ + type: 'move'; + /** The amount moved in the X direction since the last event. */ + deltaX: number; + /** The amount moved in the Y direction since the last event. */ + deltaY: number; +} + +export interface MoveEndEvent extends BaseMoveEvent { + /** The type of move event being fired. */ + type: 'moveend'; +} + +export type MoveEvent = MoveStartEvent | MoveMoveEvent | MoveEndEvent; + +export interface MoveEvents { + /** Handler that is called when a move interaction starts. */ + onMoveStart?: (e: MoveStartEvent) => void; + /** Handler that is called when the element is moved. */ + onMove?: (e: MoveMoveEvent) => void; + /** Handler that is called when a move interaction ends. */ + onMoveEnd?: (e: MoveEndEvent) => void; +} + +export interface ScrollEvent { + /** The amount moved in the X direction since the last event. */ + deltaX: number; + /** The amount moved in the Y direction since the last event. */ + deltaY: number; +} + +export interface ScrollEvents { + /** Handler that is called when the scroll wheel moves. */ + onScroll?: (e: ScrollEvent) => void; +} diff --git a/src/lib/utils/callbacks.ts b/src/lib/utils/callbacks.ts new file mode 100644 index 0000000..fca5e67 --- /dev/null +++ b/src/lib/utils/callbacks.ts @@ -0,0 +1,32 @@ +type NonEmptyArray = [T, ...T[]]; + +/** + * A callback function that takes an array of arguments of type `T` and returns `void`. + * @template T The types of the arguments that the callback function takes. + */ +export type Callback = (...args: T) => void; + +/** + * Executes an array of callback functions with the same arguments. + * @template T The types of the arguments that the callback functions take. + * @param n array of callback functions to execute. + * @returns A new function that executes all of the original callback functions with the same arguments. + */ +export function executeCallbacks( + ...callbacks: NonEmptyArray> +): (...args: T) => void { + return (...args) => { + for (const callback of callbacks) { + if (typeof callback === 'function') { + callback(...args); + } + } + }; +} + +/** + * A no operation function (does nothing) + */ +export function noop() { + // +} diff --git a/src/lib/utils/event-listeners.ts b/src/lib/utils/event-listeners.ts new file mode 100644 index 0000000..9b59f92 --- /dev/null +++ b/src/lib/utils/event-listeners.ts @@ -0,0 +1,56 @@ +/** + * A type alias for a general event listener function. + * + * @template E - The type of event to listen for + * @param evt - The event object + * @returns The return value of the event listener function + */ +export type GeneralEventListener = (evt: E) => unknown; + +/** + * Overloaded function signatures for addEventListener + */ +export function addEventListener( + target: Window, + event: E, + handler: (this: Window, ev: HTMLElementEventMap[E]) => unknown, + options?: boolean | AddEventListenerOptions +): VoidFunction; + +export function addEventListener( + target: Document, + event: E, + handler: (this: Document, ev: HTMLElementEventMap[E]) => unknown, + options?: boolean | AddEventListenerOptions +): VoidFunction; + +export function addEventListener( + target: EventTarget, + event: E, + handler: GeneralEventListener, + options?: boolean | AddEventListenerOptions +): VoidFunction; +/** + * Adds an event listener to the specified target element(s) for the given event(s), and returns a function to remove it. + * @param target The target element(s) to add the event listener to. + * @param event The event(s) to listen for. + * @param handler The function to be called when the event is triggered. + * @param options An optional object that specifies characteristics about the event listener. + * @returns A function that removes the event listener from the target element(s). + */ +export function addEventListener( + target: Window | Document | EventTarget, + event: string | string[], + handler: EventListenerOrEventListenerObject, + options?: boolean | AddEventListenerOptions +) { + const events = Array.isArray(event) ? event : [event]; + + // Add the event listener to each specified event for the target element(s). + events.forEach((_event) => target.addEventListener(_event, handler, options)); + + // Return a function that removes the event listener from the target element(s). + return () => { + events.forEach((_event) => target.removeEventListener(_event, handler, options)); + }; +} diff --git a/src/lib/utils/focus-without-scroll.ts b/src/lib/utils/focus-without-scroll.ts new file mode 100644 index 0000000..0ea7698 --- /dev/null +++ b/src/lib/utils/focus-without-scroll.ts @@ -0,0 +1,82 @@ +// Portions of the code in this file are based on code from Adobe. +// Original licensing for the following can be found: +// See https://github.com/adobe/react-spectrum/blob/main/LICENSE + +import type { FocusableElement } from '../types/dom.js'; + +// This is a polyfill for element.focus({preventScroll: true}); +// Currently necessary for Safari and old Edge: +// https://caniuse.com/#feat=mdn-api_htmlelement_focus_preventscroll_option +// See https://bugs.webkit.org/show_bug.cgi?id=178583 +// + +interface ScrollableElement { + element: HTMLElement; + scrollTop: number; + scrollLeft: number; +} + +export function focusWithoutScrolling(element: FocusableElement) { + if (supportsPreventScroll()) { + element.focus({ preventScroll: true }); + } else { + const scrollableElements = getScrollableElements(element); + element.focus(); + restoreScrollPosition(scrollableElements); + } +} + +let supportsPreventScrollCached: boolean | null = null; + +function supportsPreventScroll() { + if (supportsPreventScrollCached == null) { + supportsPreventScrollCached = false; + try { + const focusElem = document.createElement('div'); + focusElem.focus({ + get preventScroll() { + supportsPreventScrollCached = true; + return true; + } + }); + } catch (e) { + // Ignore + } + } + + return supportsPreventScrollCached; +} + +function getScrollableElements(element: FocusableElement): ScrollableElement[] { + let parent = element.parentNode; + const scrollableElements: ScrollableElement[] = []; + const rootScrollingElement = document.scrollingElement || document.documentElement; + + while (parent instanceof HTMLElement && parent !== rootScrollingElement) { + if (parent.offsetHeight < parent.scrollHeight || parent.offsetWidth < parent.scrollWidth) { + scrollableElements.push({ + element: parent, + scrollTop: parent.scrollTop, + scrollLeft: parent.scrollLeft + }); + } + parent = parent.parentNode; + } + + if (rootScrollingElement instanceof HTMLElement) { + scrollableElements.push({ + element: rootScrollingElement, + scrollTop: rootScrollingElement.scrollTop, + scrollLeft: rootScrollingElement.scrollLeft + }); + } + + return scrollableElements; +} + +function restoreScrollPosition(scrollableElements: ScrollableElement[]) { + for (const { element, scrollTop, scrollLeft } of scrollableElements) { + element.scrollTop = scrollTop; + element.scrollLeft = scrollLeft; + } +} diff --git a/src/lib/utils/get-owner.ts b/src/lib/utils/get-owner.ts new file mode 100644 index 0000000..56b0a23 --- /dev/null +++ b/src/lib/utils/get-owner.ts @@ -0,0 +1,14 @@ +export const getOwnerDocument = (el: Element | null | undefined): Document => { + return el?.ownerDocument ?? document; +}; + +export const getOwnerWindow = ( + el: (Window & typeof global) | Element | null | undefined +): Window & typeof global => { + if (el && 'window' in el && el.window === el) { + return el; + } + + const doc = getOwnerDocument(el as Element | null | undefined); + return doc.defaultView || window; +}; diff --git a/src/lib/utils/global-listeners.ts b/src/lib/utils/global-listeners.ts new file mode 100644 index 0000000..2b8b0a8 --- /dev/null +++ b/src/lib/utils/global-listeners.ts @@ -0,0 +1,95 @@ +import { safeOnDestroy } from './lifecycle.js'; + +/** + * A type alias for a general event listener function. + * + * @template E - The type of event to listen for + * @param evt - The event object + * @returns The return value of the event listener function + */ +export type GeneralEventListener = (evt: E) => unknown; + +interface GlobalListeners { + addGlobalListener( + target: Window, + event: E, + handler: (this: Window, ev: HTMLElementEventMap[E]) => unknown, + options?: boolean | AddEventListenerOptions + ): void; + + addGlobalListener( + target: Document, + event: E, + handler: (this: Document, ev: HTMLElementEventMap[E]) => unknown, + options?: boolean | AddEventListenerOptions + ): void; + addGlobalListener( + target: EventTarget, + event: E, + handler: GeneralEventListener, + options?: boolean | AddEventListenerOptions + ): void; + removeGlobalListener( + target: Window, + event: E, + handler: (this: Window, ev: HTMLElementEventMap[E]) => unknown, + options?: boolean | AddEventListenerOptions + ): void; + removeGlobalListener( + target: Document, + event: E, + handler: (this: Document, ev: HTMLElementEventMap[E]) => unknown, + options?: boolean | AddEventListenerOptions + ): void; + removeGlobalListener( + target: EventTarget, + event: E, + handler: GeneralEventListener, + options?: boolean | AddEventListenerOptions + ): void; + removeAllGlobalListeners(): void; +} + +export function createGlobalListeners(): GlobalListeners { + let globalListeners = new Map(); + + function addGlobalListener( + target: Window | Document | EventTarget, + event: string, + handler: EventListener, + options?: AddEventListenerOptions + ) { + // Make sure we remove the listener after it is called with the `once` option. + const fn = options?.once + ? (...args: Parameters) => { + globalListeners.delete(handler); + handler(...args); + } + : handler; + + globalListeners.set(handler, { event, target, fn, options }); + target.addEventListener(event, handler, options); + } + + function removeGlobalListener( + target: Window | Document | EventTarget, + event: string, + handler: EventListener, + options?: AddEventListenerOptions + ) { + const fn = globalListeners.get(handler)?.fn || handler; + target.removeEventListener(event, fn, options); + globalListeners.delete(handler); + } + + function removeAllGlobalListeners() { + globalListeners.forEach((value, key) => { + removeGlobalListener(value.target, value.event, key, value.options); + }); + globalListeners = new Map(); + } + + safeOnDestroy(removeAllGlobalListeners); + + return { addGlobalListener, removeGlobalListener, removeAllGlobalListeners }; +} diff --git a/src/lib/utils/is-virtual-event.ts b/src/lib/utils/is-virtual-event.ts new file mode 100644 index 0000000..feddd7f --- /dev/null +++ b/src/lib/utils/is-virtual-event.ts @@ -0,0 +1,46 @@ +import { isAndroid } from './platform.js'; + +// Original licensing for the following method can be found in the +// NOTICE file in the root directory of this source tree. +// See https://github.com/facebook/react/blob/3c713d513195a53788b3f8bb4b70279d68b15bcc/packages/react-interactions/events/src/dom/shared/index.js#L74-L87 + +// Keyboards, Assistive Technologies, and element.click() all produce a "virtual" +// click event. This is a method of inferring such clicks. Every browser except +// IE 11 only sets a zero value of "detail" for click events that are "virtual". +// However, IE 11 uses a zero value for all click events. For IE 11 we rely on +// the quirk that it produces click events that are of type PointerEvent, and +// where only the "virtual" click lacks a pointerType field. + +export function isVirtualClick(event: MouseEvent | PointerEvent): boolean { + // JAWS/NVDA with Firefox. + // eslint-disable-next-line @typescript-eslint/no-explicit-any + if ((event as any).mozInputSource === 0 && event.isTrusted) { + return true; + } + + // Android TalkBack's detail value varies depending on the event listener providing the event so we have specific logic here instead + // If pointerType is defined, event is from a click listener. For events from mousedown listener, detail === 0 is a sufficient check + // to detect TalkBack virtual clicks. + if (isAndroid() && (event as PointerEvent).pointerType) { + return event.type === 'click' && event.buttons === 1; + } + + return event.detail === 0 && !(event as PointerEvent).pointerType; +} + +export function isVirtualPointerEvent(event: PointerEvent) { + // If the pointer size is zero, then we assume it's from a screen reader. + // Android TalkBack double tap will sometimes return a event with width and height of 1 + // and pointerType === 'mouse' so we need to check for a specific combination of event attributes. + // Cannot use "event.pressure === 0" as the sole check due to Safari pointer events always returning pressure === 0 + // instead of .5, see https://bugs.webkit.org/show_bug.cgi?id=206216. event.pointerType === 'mouse' is to distingush + // Talkback double tap from Windows Firefox touch screen press + return ( + (!isAndroid() && event.width === 0 && event.height === 0) || + (event.width === 1 && + event.height === 1 && + event.pressure === 0 && + event.detail === 0 && + event.pointerType === 'mouse') + ); +} diff --git a/src/lib/utils/lifecycle.ts b/src/lib/utils/lifecycle.ts new file mode 100644 index 0000000..172aa37 --- /dev/null +++ b/src/lib/utils/lifecycle.ts @@ -0,0 +1,17 @@ +import { onDestroy, onMount } from 'svelte'; + +export const safeOnMount = (fn: (...args: unknown[]) => unknown) => { + try { + onMount(fn); + } catch { + return fn(); + } +}; + +export const safeOnDestroy = (fn: (...args: unknown[]) => unknown) => { + try { + onDestroy(fn); + } catch { + return fn(); + } +}; diff --git a/src/lib/utils/open-link.ts b/src/lib/utils/open-link.ts new file mode 100644 index 0000000..c19dee6 --- /dev/null +++ b/src/lib/utils/open-link.ts @@ -0,0 +1,48 @@ +import { focusWithoutScrolling } from './focus-without-scroll.js'; +import { isFirefox, isIPad, isMac, isWebKit } from './platform.js'; + +interface Modifiers { + metaKey?: boolean; + ctrlKey?: boolean; + altKey?: boolean; + shiftKey?: boolean; +} + +export function openLink(target: HTMLAnchorElement, modifiers: Modifiers, setOpening = true) { + let { metaKey, ctrlKey } = modifiers; + const { altKey, shiftKey } = modifiers; + + // Firefox does not recognize keyboard events as a user action by default, and the popup blocker + // will prevent links with target="_blank" from opening. However, it does allow the event if the + // Command/Control key is held, which opens the link in a background tab. This seems like the best we can do. + // See https://bugzilla.mozilla.org/show_bug.cgi?id=257870 and https://bugzilla.mozilla.org/show_bug.cgi?id=746640. + if (isFirefox() && window.event?.type?.startsWith('key') && target.target === '_blank') { + if (isMac()) { + metaKey = true; + } else { + ctrlKey = true; + } + } + + // WebKit does not support firing click events with modifier keys, but does support keyboard events. + // https://github.com/WebKit/WebKit/blob/c03d0ac6e6db178f90923a0a63080b5ca210d25f/Source/WebCore/html/HTMLAnchorElement.cpp#L184 + const event = + isWebKit() && isMac() && !isIPad() && process.env.NODE_ENV !== 'test' + ? // @ts-expect-error - keyIdentifier is a non-standard property, but it's what webkit expects + new KeyboardEvent('keydown', { keyIdentifier: 'Enter', metaKey, ctrlKey, altKey, shiftKey }) + : new MouseEvent('click', { + metaKey, + ctrlKey, + altKey, + shiftKey, + bubbles: true, + cancelable: true + }); + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (openLink as any).isOpening = setOpening; + focusWithoutScrolling(target); + target.dispatchEvent(event); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (openLink as any).isOpening = false; +} diff --git a/src/lib/utils/platform.ts b/src/lib/utils/platform.ts new file mode 100644 index 0000000..be3b921 --- /dev/null +++ b/src/lib/utils/platform.ts @@ -0,0 +1,56 @@ +function testUserAgent(re: RegExp) { + if (typeof window === 'undefined' || window.navigator == null) { + return false; + } + return ( + window.navigator['userAgentData']?.brands.some((brand: { brand: string; version: string }) => + re.test(brand.brand) + ) || re.test(window.navigator.userAgent) + ); +} + +function testPlatform(re: RegExp) { + return typeof window !== 'undefined' && window.navigator != null + ? re.test(window.navigator['userAgentData']?.platform || window.navigator.platform) + : false; +} + +export function isMac() { + return testPlatform(/^Mac/i); +} + +export function isIPhone() { + return testPlatform(/^iPhone/i); +} + +export function isIPad() { + return ( + testPlatform(/^iPad/i) || + // iPadOS 13 lies and says it's a Mac, but we can distinguish by detecting touch support. + (isMac() && navigator.maxTouchPoints > 1) + ); +} + +export function isIOS() { + return isIPhone() || isIPad(); +} + +export function isAppleDevice() { + return isMac() || isIOS(); +} + +export function isWebKit() { + return testUserAgent(/AppleWebKit/i) && !isChrome(); +} + +export function isChrome() { + return testUserAgent(/Chrome/i); +} + +export function isAndroid() { + return testUserAgent(/Android/i); +} + +export function isFirefox() { + return testUserAgent(/Firefox/i); +} diff --git a/src/lib/utils/run-after-transition.ts b/src/lib/utils/run-after-transition.ts new file mode 100644 index 0000000..e6e10b4 --- /dev/null +++ b/src/lib/utils/run-after-transition.ts @@ -0,0 +1,83 @@ +// We store a global list of elements that are currently transitioning, +// mapped to a set of CSS properties that are transitioning for that element. +// This is necessary rather than a simple count of transitions because of browser +// bugs, e.g. Chrome sometimes fires both transitionend and transitioncancel rather +// than one or the other. So we need to track what's actually transitioning so that +// we can ignore these duplicate events. +const transitionsByElement = new Map>(); + +// A list of callbacks to call once there are no transitioning elements. +const transitionCallbacks = new Set<() => void>(); + +function setupGlobalEvents() { + if (typeof window === 'undefined') { + return; + } + + function onTransitionStart(e: TransitionEvent) { + // Add the transitioning property to the list for this element. + if (!e.target) return; + let transitions = transitionsByElement.get(e.target); + if (!transitions) { + transitions = new Set(); + transitionsByElement.set(e.target, transitions); + + // The transitioncancel event must be registered on the element itself, rather than as a global + // event. This enables us to handle when the node is deleted from the document while it is transitioning. + // In that case, the cancel event would have nowhere to bubble to so we need to handle it directly. + e.target.addEventListener('transitioncancel', onTransitionEnd as EventListener); + } + + transitions.add(e.propertyName); + } + + function onTransitionEnd(e: TransitionEvent) { + // Remove property from list of transitioning properties. + if (!e.target) return; + const properties = transitionsByElement.get(e.target); + if (!properties) { + return; + } + + properties.delete(e.propertyName); + + // If empty, remove transitioncancel event, and remove the element from the list of transitioning elements. + if (properties.size === 0) { + e.target.removeEventListener('transitioncancel', onTransitionEnd as EventListener); + transitionsByElement.delete(e.target); + } + + // If no transitioning elements, call all of the queued callbacks. + if (transitionsByElement.size === 0) { + for (const cb of transitionCallbacks) { + cb(); + } + + transitionCallbacks.clear(); + } + } + + document.body.addEventListener('transitionrun', onTransitionStart); + document.body.addEventListener('transitionend', onTransitionEnd); +} + +if (typeof document !== 'undefined') { + if (document.readyState !== 'loading') { + setupGlobalEvents(); + } else { + document.addEventListener('DOMContentLoaded', setupGlobalEvents); + } +} + +export function runAfterTransition(fn: () => void) { + // Wait one frame to see if an animation starts, e.g. a transition on mount. + requestAnimationFrame(() => { + // If no transitions are running, call the function immediately. + // Otherwise, add it to a list of callbacks to run at the end of the animation. + if (transitionsByElement.size === 0) { + fn(); + } else { + transitionCallbacks.add(fn); + } + }); +} diff --git a/src/lib/utils/text-selection.ts b/src/lib/utils/text-selection.ts new file mode 100644 index 0000000..9b9a7fb --- /dev/null +++ b/src/lib/utils/text-selection.ts @@ -0,0 +1,88 @@ +import { getOwnerDocument } from './get-owner.js'; +import { isIOS } from './platform.js'; +import { runAfterTransition } from './run-after-transition.js'; + +// Safari on iOS starts selecting text on long press. The only way to avoid this, it seems, +// is to add user-select: none to the entire page. Adding it to the pressable element prevents +// that element from being selected, but nearby elements may still receive selection. We add +// user-select: none on touch start, and remove it again on touch end to prevent this. +// This must be implemented using global state to avoid race conditions between multiple elements. + +// There are three possible states due to the delay before removing user-select: none after +// pointer up. The 'default' state always transitions to the 'disabled' state, which transitions +// to 'restoring'. The 'restoring' state can either transition back to 'disabled' or 'default'. + +// For non-iOS devices, we apply user-select: none to the pressed element instead to avoid possible +// performance issues that arise from applying and removing user-select: none to the entire page +type State = 'default' | 'disabled' | 'restoring'; + +// Note that state only matters here for iOS. Non-iOS gets user-select: none applied to the target element +// rather than at the document level so we just need to apply/remove user-select: none for each pressed element individually +let state: State = 'default'; +let savedUserSelect = ''; +const modifiedElementMap = new WeakMap(); + +export function disableTextSelection(target?: Element) { + if (isIOS()) { + if (state === 'default') { + // eslint-disable-next-line no-restricted-globals + const documentObject = getOwnerDocument(target); + savedUserSelect = documentObject.documentElement.style.webkitUserSelect; + documentObject.documentElement.style.webkitUserSelect = 'none'; + } + + state = 'disabled'; + } else if (target instanceof HTMLElement || target instanceof SVGElement) { + // If not iOS, store the target's original user-select and change to user-select: none + // Ignore state since it doesn't apply for non iOS + modifiedElementMap.set(target, target.style.userSelect); + target.style.userSelect = 'none'; + } +} + +export function restoreTextSelection(target?: Element) { + if (isIOS()) { + // If the state is already default, there's nothing to do. + // If it is restoring, then there's no need to queue a second restore. + if (state !== 'disabled') { + return; + } + + state = 'restoring'; + + // There appears to be a delay on iOS where selection still might occur + // after pointer up, so wait a bit before removing user-select. + setTimeout(() => { + // Wait for any CSS transitions to complete so we don't recompute style + // for the whole page in the middle of the animation and cause jank. + runAfterTransition(() => { + // Avoid race conditions + if (state === 'restoring') { + // eslint-disable-next-line no-restricted-globals + const documentObject = getOwnerDocument(target); + if (documentObject.documentElement.style.webkitUserSelect === 'none') { + documentObject.documentElement.style.webkitUserSelect = savedUserSelect || ''; + } + + savedUserSelect = ''; + state = 'default'; + } + }); + }, 300); + } else if (target instanceof HTMLElement || target instanceof SVGElement) { + // If not iOS, restore the target's original user-select if any + // Ignore state since it doesn't apply for non iOS + if (target && modifiedElementMap.has(target)) { + const targetOldUserSelect = modifiedElementMap.get(target) as string; + + if (target.style.userSelect === 'none') { + target.style.userSelect = targetOldUserSelect; + } + + if (target.getAttribute('style') === '') { + target.removeAttribute('style'); + } + modifiedElementMap.delete(target); + } + } +} diff --git a/src/lib/utils/to-writable-stores.ts b/src/lib/utils/to-writable-stores.ts new file mode 100644 index 0000000..f01de65 --- /dev/null +++ b/src/lib/utils/to-writable-stores.ts @@ -0,0 +1,23 @@ +import { type Writable, writable } from 'svelte/store'; + +export type ToWritableStores> = { + [K in keyof T]: Writable; +}; + +/** + * Given an object of properties, returns an object of writable stores + * with the same properties and values. + */ +export function toWritableStores>( + properties: T +): ToWritableStores { + const result = {} as { [K in keyof T]: Writable }; + + Object.keys(properties).forEach((key) => { + const propertyKey = key as keyof T; + const value = properties[propertyKey]; + result[propertyKey] = writable(value); + }); + + return result; +} diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte new file mode 100644 index 0000000..2d19a0a --- /dev/null +++ b/src/routes/+page.svelte @@ -0,0 +1,23 @@ + + + diff --git a/static/favicon.png b/static/favicon.png new file mode 100644 index 0000000..825b9e6 Binary files /dev/null and b/static/favicon.png differ diff --git a/svelte.config.js b/svelte.config.js new file mode 100644 index 0000000..2b35fe1 --- /dev/null +++ b/svelte.config.js @@ -0,0 +1,18 @@ +import adapter from '@sveltejs/adapter-auto'; +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; + +/** @type {import('@sveltejs/kit').Config} */ +const config = { + // Consult https://kit.svelte.dev/docs/integrations#preprocessors + // for more information about preprocessors + preprocess: vitePreprocess(), + + kit: { + // adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list. + // If your environment is not supported or you settled on a specific environment, switch out the adapter. + // See https://kit.svelte.dev/docs/adapters for more information about adapters. + adapter: adapter() + } +}; + +export default config; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..5cd5063 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "./.svelte-kit/tsconfig.json", + "compilerOptions": { + "allowJs": true, + "checkJs": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "sourceMap": true, + "strict": true, + "module": "NodeNext", + "moduleResolution": "NodeNext", + "types": ["./node_modules/user-agent-data-types"] + } +} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..37b6a84 --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,9 @@ +import { sveltekit } from '@sveltejs/kit/vite'; +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + plugins: [sveltekit()], + test: { + include: ['src/**/*.{test,spec}.{js,ts}'] + } +});