diff --git a/.firebaserc b/.firebaserc new file mode 100644 index 0000000..d2502f9 --- /dev/null +++ b/.firebaserc @@ -0,0 +1,14 @@ +{ + "projects": { + "default": "zeta-ds" + }, + "targets": { + "zeta-ds": { + "hosting": { + "web": [ + "zeta-web-main" + ] + } + } + } +} \ No newline at end of file diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..30899a3 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,4 @@ +/.yarn/releases/** binary +/.yarn/plugins/** binary +/.yarn/cache/** binary +*.json linguist-language=JSON-with-Comments \ No newline at end of file diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..7f890c5 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @benken @thelukewalton @mikecoomber \ No newline at end of file diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..7b5379d --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,36 @@ +name: "CodeQL" + +on: + push: + branches: + - "main" + pull_request: + branches: + - "main" + schedule: + - cron: "24 8 * * 0" + +jobs: + codeql: + name: Analyze Code Quality + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + strategy: + fail-fast: false + matrix: + language: ["javascript"] + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language }} + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 + with: + category: "/language:${{matrix.language}}" diff --git a/.github/workflows/create-figma-links.yml b/.github/workflows/create-figma-links.yml new file mode 100644 index 0000000..d79ca16 --- /dev/null +++ b/.github/workflows/create-figma-links.yml @@ -0,0 +1,21 @@ +name: Create documentation links on Figma + +on: workflow_dispatch + +jobs: + link-docs: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 + registry-url: "https://registry.npmjs.org" + - name: Run npm i + run: npm ci + - name: Create custom-elements.json + run: npm run analyze + - name: Create documentation links + uses: ./.github/workflows/link-docs + with: + figma-access-token: ${{ secrets.FIGMA_TOKEN }} diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml new file mode 100644 index 0000000..2167bb1 --- /dev/null +++ b/.github/workflows/dependency-review.yml @@ -0,0 +1,14 @@ +name: "Dependency Review" +on: [pull_request] + +permissions: + contents: read + +jobs: + dependency-review: + runs-on: ubuntu-latest + steps: + - name: "Checkout Repository" + uses: actions/checkout@v4 + - name: "Dependency Review" + uses: actions/dependency-review-action@v3 diff --git a/.github/workflows/link-docs/action.yml b/.github/workflows/link-docs/action.yml new file mode 100644 index 0000000..8517329 --- /dev/null +++ b/.github/workflows/link-docs/action.yml @@ -0,0 +1,9 @@ +name: "Create documentation links" +description: "Create storybook links on the Figma components" +inputs: + figma-access-token: + description: "The personal access token given by Figma. Required to make API calls" + required: true +runs: + using: "node20" + main: "index.js" diff --git a/.github/workflows/link-docs/index.js b/.github/workflows/link-docs/index.js new file mode 100644 index 0000000..9fe4d4a --- /dev/null +++ b/.github/workflows/link-docs/index.js @@ -0,0 +1,59 @@ +import core from "@actions/core"; +import manifest from "../../../custom-elements.json" assert { type: "json" }; + +const FILE_KEY = "JesXQFLaPJLc1BdBM4sisI"; + +const postDevResources = async links => { + const url = "https://api.figma.com/v1/dev_resources"; + const response = await fetch(url, { + method: "POST", + body: JSON.stringify({ + dev_resources: links.map(link => { + return { name: "Storybook", url: link.storybook, node_id: link.nodeId, file_key: FILE_KEY }; + }) + }), + headers: { + "Content-type": "application/json; charset=UTF-8", + "X-FIGMA-TOKEN": core.getInput("figma-access-token") + } + }); + + console.log(await response.json()); + if (response.status != 200) { + core.setFailed(response.statusText); + } +}; + +const getNodeId = figmaUrl => { + const paramsStr = figmaUrl.split("?")[1]; + const params = paramsStr.split("&"); + const nodeIdParam = params.find(param => param.split("=")[0] == "node-id"); + const nodeId = nodeIdParam.split("=")[1]; + return nodeId.replace(/-/g, ":").replace(/\%3A/g, ":"); +}; + +const main = () => { + const links = []; + manifest.modules.forEach(module => { + const classDec = module.declarations.find(dec => dec.kind == "class"); + if (classDec?.figma && classDec?.storybook) { + if (Array.isArray(classDec.figma)) { + links.push( + ...classDec.figma.map(figmaLink => { + return { nodeId: getNodeId(figmaLink.name), storybook: classDec?.storybook.name }; + }) + ); + } else { + links.push({ + nodeId: getNodeId(classDec.figma.name), + storybook: classDec?.storybook.name + }); + } + } else if (classDec) { + console.warn(`${classDec.name} is missing documentation links`); + } + }); + postDevResources(links); +}; + +main(); diff --git a/.github/workflows/on-main.yml b/.github/workflows/on-main.yml new file mode 100644 index 0000000..f0e14c5 --- /dev/null +++ b/.github/workflows/on-main.yml @@ -0,0 +1,42 @@ +name: CI - On Main + +on: + workflow_dispatch: + push: + branches: + - main + +permissions: + contents: write + pull-requests: write + +jobs: + release-please: + runs-on: ubuntu-latest + steps: + - uses: googleapis/release-please-action@v4 + with: + token: ${{ secrets.PERSONAL_ACCESS_TOKEN }} + config-file: release-please-config.json + manifest-file: .release-please-manifest.json + deploy-qa-demo: + name: Deploy preview version of the storybook on firebase + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Setup node + uses: actions/setup-node@v3 + with: + node-version: 18.x.x + cache: "npm" + - name: Install dependencies + run: npm ci + - name: Create custom-elements.json + run: npm run analyze + - name: Build storybook + run: npm run storybook:build + - uses: FirebaseExtended/action-hosting-deploy@v0 + with: + repoToken: "${{ secrets.GITHUB_TOKEN }}" + firebaseServiceAccount: "${{ secrets.FIREBASE_SERVICE_ACCOUNT_ZETA_DS }}" + channelId: "live" diff --git a/.github/workflows/on-release.yml b/.github/workflows/on-release.yml new file mode 100644 index 0000000..61a3142 --- /dev/null +++ b/.github/workflows/on-release.yml @@ -0,0 +1,26 @@ +name: CI - On Release + +on: + workflow_dispatch: + release: + types: [released] + +jobs: + deploy-website: + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - uses: actions/github-script@v7 + with: + github-token: ${{ secrets.PERSONAL_ACCESS_TOKEN }} + script: | + await github.rest.actions.createWorkflowDispatch({ + owner: 'zebratechnologies', + repo: 'zeta', + workflow_id: 'deploy.yml', + ref: 'main', + }) + + publish-web: + uses: ./.github/workflows/publish-web.yml + secrets: inherit diff --git a/.github/workflows/publish-web.yml b/.github/workflows/publish-web.yml new file mode 100644 index 0000000..d8df687 --- /dev/null +++ b/.github/workflows/publish-web.yml @@ -0,0 +1,21 @@ +name: CI - Publish web +on: + workflow_dispatch: + workflow_call: + secrets: + NPM_TOKEN: + required: true +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v3 + - name: Install packages + run: npm ci + - name: Compile Typescript files + run: npm run build + - name: Publish + run: npm publish + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml new file mode 100644 index 0000000..1fdf43c --- /dev/null +++ b/.github/workflows/pull-request.yml @@ -0,0 +1,125 @@ +name: CI - Pull Request + +on: pull_request + +# Pull Request Runs on the same branch will be cancelled +concurrency: + group: ${{ github.head_ref }} + cancel-in-progress: true + +jobs: + code-quality: + name: Check the code quality + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: ${{ github.head_ref }} + - name: Setup node + uses: actions/setup-node@v3 + with: + node-version: 18.x.x + cache: "npm" + - name: Install dependencies + run: npm ci + - name: Store Playwright's Version + id: store-playwright-version + run: | + PLAYWRIGHT_VERSION=$(npm ls @playwright/test | grep @playwright | sed 's/.*@//') + echo "Playwright's Version: $PLAYWRIGHT_VERSION" + echo "PLAYWRIGHT_VERSION=$PLAYWRIGHT_VERSION" >> $GITHUB_ENV + - name: Cache Playwright Browsers for Playwright's Version + id: cache-playwright-browsers + uses: actions/cache@v3 + env: + PLAYWRIGHT_VERSION: ${{ steps.store-playwright-version.outputs.PLAYWRIGHT_VERSION }} + if: env.PLAYWRIGHT_VERSION == 'true' + with: + path: ~/.cache/ms-playwright + key: playwright-browsers-${{ env.PLAYWRIGHT_VERSION }} + - name: Install playwright browsers + if: steps.cache-playwright-browsers.outputs.cache-hit != 'true' + run: npx playwright install --with-deps + - name: Run custom elements manifest analyzer + run: npm run analyze + - name: Run eslint + run: npm run lint + - name: Run prettier + run: npm run prettier + - name: Run lit-analyzer + run: npm run lint:lit-analyzer + - name: Run tests + run: npm run test -- --debug + - name: Test tsdoc + run: npm run docs + - name: Check for modified files + id: git-check + run: echo "modified=$(if [ -n "$(git status --porcelain)" ]; then echo "true"; else echo "false"; fi)" >> $GITHUB_ENV + - name: Update changes in GitHub repository + env: + MODIFIED: ${{ steps.git-check.outputs.modified }} + if: env.MODIFIED == 'true' + run: | + git config --global user.name "github-actions" + git config --global user.email "github-actions@github.com" + git add -A + git commit -m '[automated commit] lint format and import sort' + git push + generate-localizations: + name: Generate localizations. + needs: [code-quality] + timeout-minutes: 20 + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.ref }} + - name: Setup node + uses: actions/setup-node@v3 + with: + node-version: 18.x.x + cache: "npm" + - name: Install dependencies + run: npm ci + - name: Run lit-localize extract + run: npm run localize:extract + - name: Run lit-localize build + run: npm run localize:build + - name: Check diff + id: diff + run: git diff --quiet . || echo "changed=true" >> $GITHUB_OUTPUT + - name: Commit files + if: steps.diff.outputs.changed == 'true' + run: | + git config --global user.name 'github-actions' + git config --global user.email 'github-actions@github.com' + git add . + git commit -m "Generated locales" + git push + deploy-preview: + timeout-minutes: 20 + name: Deploy preview version of the storybook on firebase + needs: [generate-localizations] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Setup node + uses: actions/setup-node@v3 + with: + node-version: 18.x.x + cache: "npm" + - name: Install dependencies + run: npm ci + - name: Create custom-elements.json + run: npm run analyze + - name: Build storybook + run: npm run storybook:build + - uses: FirebaseExtended/action-hosting-deploy@v0 + with: + repoToken: "${{ secrets.GITHUB_TOKEN }}" + firebaseServiceAccount: "${{ secrets.FIREBASE_SERVICE_ACCOUNT_ZETA_DS }}" + expires: 7d + channelId: "pr-${{ github.event.number }}-${{ github.event.pull_request.head.ref }}" diff --git a/.github/workflows/update-icons.yml b/.github/workflows/update-icons.yml new file mode 100644 index 0000000..98306af --- /dev/null +++ b/.github/workflows/update-icons.yml @@ -0,0 +1,29 @@ +name: Update Zeta Icons + +on: + workflow_dispatch: + repository_dispatch: + types: [update-icons] + +jobs: + update: + runs-on: ubuntu-latest + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + - name: Setup node + uses: actions/setup-node@v3 + with: + node-version: 18.x.x + cache: "npm" + - name: Update Zeta Icons + run: npm install @zebra-fed/zeta-icons@latest --save + - name: Create Pull Request + uses: peter-evans/create-pull-request@v5 + with: + commit-message: "deps: Update zeta-icons library" + branch: "update-zeta-icons" + base: "main" + delete-branch: true + title: "deps: Update zeta-icons library" + labels: "icons" diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9fc7c28 --- /dev/null +++ b/.gitignore @@ -0,0 +1,232 @@ +/node_modules +/build +/dist +/output +.yarn/* +!.yarn/cache +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/sdks +!.yarn/ +/docs/* +custom-elements.json +zeta-web.code-workspace + +### macOS ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### macOS Patch ### +# iCloud generated files +*.icloud + +### Node ### +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* +test/package-lock.json + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# vuepress v2.x temp and cache directory +.temp + +# Docusaurus cache and generated files +.docusaurus + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v2 +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* + +### Node Patch ### +# Serverless Webpack directories +.webpack/ + +# Optional stylelint cache + +# SvelteKit build / generate output +.svelte-kit + +### react ### +.DS_* +**/*.backup.* +**/*.back.* + +node_modules + +*.sublime* + +psd +thumb +sketch + +### StorybookJs ### +# gitignore template for the Storybook, UI guide for front apps +# website: https://storybook.js.org/ + +storybook-static/ + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# End of https://www.toptal.com/developers/gitignore/api/macos,windows,node,react,web,storybookjs +node_modules +.firebase/ \ No newline at end of file diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..f2ee0c4 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,2 @@ +src/generated +*.json \ No newline at end of file diff --git a/.release-please-manifest.json b/.release-please-manifest.json new file mode 100644 index 0000000..2be9c43 --- /dev/null +++ b/.release-please-manifest.json @@ -0,0 +1,3 @@ +{ + ".": "0.2.0" +} diff --git a/.storybook/ZetaTheme.js b/.storybook/ZetaTheme.js new file mode 100644 index 0000000..9aee590 --- /dev/null +++ b/.storybook/ZetaTheme.js @@ -0,0 +1,73 @@ +// .storybook/YourTheme.js + +import { create } from "@storybook/theming"; +import logoDark from "./logo-dark.svg"; +import logoLight from "./logo-light.svg"; + +const shared = { + // Typography + fontCode: "monospace", + + inputBorderRadius: 0, + + brandTitle: "Zeta Design System", + brandUrl: "https://www.zebra.com", + brandTarget: "_self", +}; + +export default { + light: create({ + ...shared, + ...{ + base: "light", + brandImage: logoLight, + + // UI + appBg: "white", + appBorderColor: "grey", + appBorderRadius: 0, + + // Toolbar default and active colors + barTextColor: "white", + barSelectedColor: "#E0E3E9", + barBg: "#1d1e23", + + // Form colors + inputBg: "white", + inputBorder: "silver", + inputTextColor: "black", + + // Text colors + textColor: "#1d1e23", //"var(--main-default)", + textInverseColor: "#ffffff", //"var(--main-inverse)", + colorPrimary: "#1d1e23", //"var(--color-cool-90)" + }, + }), + dark: create({ + ...shared, + ...{ + base: "dark", + brandImage: logoDark, + + // UI + appBg: "black", + appBorderColor: "grey", + appBorderRadius: 0, + + // Toolbar default and active colors + barTextColor: "white", + barSelectedColor: "#E0E3E9", + barBg: "#1d1e23", + + // Form colors + inputBg: "white", + inputBorder: "silver", + inputTextColor: "black", + + // Text colors + textColor: "#f8fbff", //"var(--main-default)", + textInverseColor: "#000000", //"var(--main-inverse)", + colorPrimary: "#f3f6fa", //"var(--color-cool-90)" + }, + }), +}; diff --git a/.storybook/extractArgs.ts b/.storybook/extractArgs.ts new file mode 100644 index 0000000..6adffaa --- /dev/null +++ b/.storybook/extractArgs.ts @@ -0,0 +1,163 @@ +/** + * Adapted from cem-plugin-better-lit-types: https://github.com/Uscreen-video/cem-plugin-better-lit-types. + */ + +import { reduceTypes } from "cem-plugin-better-lit-types/storybook"; +import { FIELD } from "../node_modules/cem-plugin-better-lit-types/dist/extractor/types"; + +/** + * Extracts arguments for a component based on its manifest and mapArgs. + * @param {object} manifest - The component's manifest. + * @param {object} mapArgs - The mapArgs object. + * @returns {object} - The extracted arguments for the component. + */ +export default (manifest, mapArgs?) => (componentName) => { + const declaration: any = getDeclaration(manifest, componentName); + + if (!declaration) return; + + let args = Object.fromEntries( + Object.entries( + Object.assign( + Object.assign( + Object.assign( + Object.assign( + {}, + reduceTypes(declaration.attributes, FIELD.attributes, mapArgs) + ), + reduceTypes(declaration.members, FIELD.properties, mapArgs) + ), + reduceTypes(declaration.slots, FIELD.slots, mapArgs) + ), + reduceTypes(declaration.cssProperties, FIELD.css, mapArgs) + ) + ).map((e) => { + /** Edit fields after initial creation */ + return e.map((f) => { + if ( + f! && + f!["table"] && + f!["name"] && + f!["table"]["category"] && + f!["table"]["category"] === "slots" && + declaration["slots"] + ) { + const name = f!["name"] === "default" ? "" : f!["name"]; + if (declaration["slots"].some((e) => e["name"] === name)) { + const decValue = declaration["slots"].find( + (e) => e["name"] === name + ); + + if (decValue["type"]) f!["type"] = decValue["type"]; + } + } + + /** Move value of `type.text` to `type`. */ + if ( + f!["type"] && + f!["type"]["text"] && + !f!["type"]["text"].includes("boolean") + ) { + f!["type"] = f!["type"]["text"]; + } + + /** Set values for boolean */ + if ( + f!["type"] && + f!["type"]["text"] && + f!["type"]["text"].includes("boolean") + ) { + f!["type"] = "boolean"; + f!["control"] = { type: "boolean" }; + } + + /** Set controls for plain strings. */ + if ( + f!["type"] && + typeof f!["type"] === "string" && + f!["type"].includes("string") + ) { + f!["control"] = { type: "text" }; + } + + /** If the name includes color, set the control to color. */ + if (f!["name"] && f!["name"].includes("color")) { + f!["control"] = { type: "color" }; + } + + /** Storybook doesn't play nicely with `undefined` so change to a string. */ + if ( + f!["type"] && + typeof f!["type"] === "string" && + f!["type"].includes(" | undefined") && + f!["table"]["defaultValue"]["summary"] === undefined + ) { + f!["table"]["defaultValue"]["summary"] = "undefined"; + } + + return f; + }); + }) + ); + + if (declaration.events) { + args = getEvents(declaration, args); + } + + return args; +}; + +/** + * Adds event values to `args`. + * + * Builds values using `events` in `declaration` and adds them to `args` output. + * @param {object} declaration - The component's declaration. + * @param {object} args - The arguments object. + * @returns {object} - The updated arguments object. + */ +const getEvents = (declaration, args) => { + declaration.events.forEach((element) => { + args[element.name] = { + name: element?.name, + description: element?.description, + table: { category: "events", defaultValue: "undefined" }, + type: element?.type?.text, + }; + }); + + return args; +}; + + +/** + * Gets the declaration for a component based on its manifest and tagName. + * @param {object} manifest - The component's manifest. + * @param {string} tagName - The component's tagName. + * @param {string} type - The type of declaration (optional). + * @returns {object} - The component's declaration. + */ +export const getDeclaration = (manifest, tagName, type = "") => { + var _a; + let _declaration; + const finder = type === "mixin" ? "name" : "tagName"; + + (_a = + manifest === null || manifest === void 0 ? void 0 : manifest.modules) === + null || _a === void 0 + ? void 0 + : _a.forEach((_module) => { + var _a; + (_a = + _module === null || _module === void 0 + ? void 0 + : _module.declarations) === null || _a === void 0 + ? void 0 + : _a.forEach((declaration) => { + if (declaration[finder] === tagName) { + _declaration = declaration; + } + }); + }); + + return _declaration; +}; diff --git a/.storybook/logo-dark.svg b/.storybook/logo-dark.svg new file mode 100644 index 0000000..a20925d --- /dev/null +++ b/.storybook/logo-dark.svg @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.storybook/logo-light.svg b/.storybook/logo-light.svg new file mode 100644 index 0000000..c1225a4 --- /dev/null +++ b/.storybook/logo-light.svg @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.storybook/main.ts b/.storybook/main.ts new file mode 100644 index 0000000..2bbb01f --- /dev/null +++ b/.storybook/main.ts @@ -0,0 +1,29 @@ +import type { StorybookConfig } from "@storybook/web-components-vite"; +import { mergeConfig } from "vite"; + +const config: StorybookConfig = { + stories: [ + "../src/stories/**/*.mdx", + "../src/stories/**/*.stories.@(js|jsx|mjs|ts|tsx)", + ], + addons: [ + "@storybook/addon-links", + "@storybook/addon-essentials", + "@storybook/addon-a11y", + "@storybook/addon-designs", + "@etchteam/storybook-addon-status", + ], + framework: { + name: "@storybook/web-components-vite", + options: {}, + }, + docs: { + autodocs: "tag", + }, + core: { + disableTelemetry: true, + }, + + staticDirs: ["../assets/"], +}; +export default config; diff --git a/.storybook/manager-head.html b/.storybook/manager-head.html new file mode 100644 index 0000000..165fd8d --- /dev/null +++ b/.storybook/manager-head.html @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/.storybook/manager.js b/.storybook/manager.js new file mode 100644 index 0000000..f9a2b1a --- /dev/null +++ b/.storybook/manager.js @@ -0,0 +1,13 @@ +import themes from "./ZetaTheme"; +import { addons } from '@storybook/manager-api'; +const setDarkMode = (isDarkMode) => { + addons.setConfig({ + theme: isDarkMode ? themes.dark : themes.light + }); +} + +window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', event => { + setDarkMode(event.matches); +}); + +setDarkMode(window.matchMedia('(prefers-color-scheme: dark)').matches); \ No newline at end of file diff --git a/.storybook/preview-head.html b/.storybook/preview-head.html new file mode 100644 index 0000000..e42307e --- /dev/null +++ b/.storybook/preview-head.html @@ -0,0 +1,202 @@ + + + + + + diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx new file mode 100644 index 0000000..ade35a8 --- /dev/null +++ b/.storybook/preview.tsx @@ -0,0 +1,99 @@ +import type { Preview } from "@storybook/web-components"; +import { themes } from "@storybook/theming"; +import { setCustomElementsManifest } from "@storybook/web-components"; +import customElements from "../custom-elements.json"; +import "../src"; +import { Title, Description, Primary, Stories, ArgTypes } from "@storybook/addon-docs"; +import React from "react"; +import extractArgs from "./extractArgs"; +import { createLitRenderer } from "cem-plugin-better-lit-types/storybook"; +import "@zebra-fed/zeta-icons/index.css"; + +setCustomElementsManifest(customElements); + +const preview: Preview = { + parameters: { + actions: { argTypesRegex: "^on[A-Z].*" }, + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/, + }, + }, + design: { + type: "figma", + }, + docs: { + extractArgTypes: extractArgs(customElements), + theme: window.matchMedia("(prefers-color-scheme: dark)").matches ? themes.dark : themes.light, + page: () => { + return ( + <> + + <Primary /> + <Description /> + <ArgTypes /> + <Stories includePrimary={false} /> + </> + ); + }, + }, + + options: { + storySort: { + method: "alphabetical", + order: ["Introduction", "Docs"], + locales: "", + }, + }, + status: { + statuses: { + designPending: { + background: "#FEF0F1", + color: "#F36070", + description: "Design requires updates", + }, + needsAttention: { + background: "#FEF0F1", + color: "#F36070", + description: "Component requires updates", + }, + inProgress: { + background: "#FEF2E2", + color: "#F5A230", + description: "In progress", + }, + ready: { + background: "#ECFFF7", + color: "#67B796", + description: "Web Component ready to use", + }, + }, + }, + viewport: { + viewports: { + android: { + name: "Android", + styles: { + width: "360px", + height: "640px", + }, + }, + iPhone: { + name: "iPhone 11 Pro", + styles: { + width: "375px", + height: "812px", + }, + }, + }, + }, + }, +}; + +export default preview; + +/// Parses slots in storybook correctly. +export const render = createLitRenderer({ + wrapSlots: true, +}); diff --git a/.storybook/test-runner.ts b/.storybook/test-runner.ts new file mode 100644 index 0000000..4b5d8d0 --- /dev/null +++ b/.storybook/test-runner.ts @@ -0,0 +1,22 @@ +import type { TestRunnerConfig } from '@storybook/test-runner'; +import { injectAxe, checkA11y } from 'axe-playwright'; + +/* + * See https://storybook.js.org/docs/writing-tests/test-runner#test-hook-api + * to learn more about the test-runner hooks API. + */ +const config: TestRunnerConfig = { + async preVisit(page) { + await injectAxe(page); + }, + async postVisit(page) { + await checkA11y(page, '#storybook-root', { + detailedReport: false, + detailedReportOptions: { + html: false, + }, + }); + }, +}; + +export default config; diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..daaa5ee --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,7 @@ +{ + "recommendations": [ + "arcanis.vscode-zipfs", + "dbaeumer.vscode-eslint", + "esbenp.prettier-vscode" + ] +} diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..868c3cc --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,17 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "node", + "request": "launch", + "name": "Launch Storybook", + "runtimeExecutable": "npm", + "cwd": "${workspaceFolder}", + "runtimeArgs": ["run", "storybook"], + "console": "integratedTerminal", + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..322cf61 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,11 @@ +{ + "search.exclude": { + "**/.yarn": true, + "**/.pnp.*": true + }, + + "typescript.enablePromptUseWorkspaceTsdk": true, + "files.associations": { + "*.json": "jsonc" + } +} diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..d06d8f5 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,111 @@ +# Changelog + +## [0.2.0](https://github.com/zebratechnologies/zeta-web/compare/zeta-web-v0.1.0...zeta-web-v0.2.0) (2024-06-10) + + +### ⚠ BREAKING CHANGES + +* renamed the package to "@zebra-fed/zeta-web" + +### ✨ New Features + +* add primitives / semantics css ([#93](https://github.com/zebratechnologies/zeta-web/issues/93)) ([319b078](https://github.com/zebratechnologies/zeta-web/commit/319b078e8fa8e414f18940957e201df1aa89b978)) +* **build:** Components can now be imported individually ([bf3c39e](https://github.com/zebratechnologies/zeta-web/commit/bf3c39ec40cc9d3e1bc63c742e0ff38a7396e1b4)) +* **build:** Exports/built files now can be used individually ([bf3c39e](https://github.com/zebratechnologies/zeta-web/commit/bf3c39ec40cc9d3e1bc63c742e0ff38a7396e1b4)) +* Button Group ([#100](https://github.com/zebratechnologies/zeta-web/issues/100)) ([133dbd0](https://github.com/zebratechnologies/zeta-web/commit/133dbd00c940b618617dfea02aadf53d56a6d040)) +* moved styles to css-in-js ([bf3c39e](https://github.com/zebratechnologies/zeta-web/commit/bf3c39ec40cc9d3e1bc63c742e0ff38a7396e1b4)) +* zeta-react auto generation ([#114](https://github.com/zebratechnologies/zeta-web/issues/114)) ([240a4c8](https://github.com/zebratechnologies/zeta-web/commit/240a4c8e28fd86f677718ff3962655e3ad24da4d)) +* zeta-react auto generation ([#114](https://github.com/zebratechnologies/zeta-web/issues/114)) ([685be62](https://github.com/zebratechnologies/zeta-web/commit/685be623674ac8535266996d7ec2b303a2cdaa14)) + + +### 🪲 Bug Fixes + +* added `showDropdown` to button-group-item ([bf3c39e](https://github.com/zebratechnologies/zeta-web/commit/bf3c39ec40cc9d3e1bc63c742e0ff38a7396e1b4)) +* update font values ([#106](https://github.com/zebratechnologies/zeta-web/issues/106)) ([506f70d](https://github.com/zebratechnologies/zeta-web/commit/506f70d0c28160b165221810eb67dffdd27a0a69)) + + +### 📈 Documentation + +* add documentation for events ([#107](https://github.com/zebratechnologies/zeta-web/issues/107)) ([62b1b2a](https://github.com/zebratechnologies/zeta-web/commit/62b1b2a31bf1192354b68634b7db0ba102900410)) +* add extractArgs script adapted from cem-plugin-better-lit-types ([#98](https://github.com/zebratechnologies/zeta-web/issues/98)) ([7594fcb](https://github.com/zebratechnologies/zeta-web/commit/7594fcb5b3342ba862661e7ee4d2a9872f91b7f8)) +* Add figma links / statuses to stories ([#95](https://github.com/zebratechnologies/zeta-web/issues/95)) ([f62b401](https://github.com/zebratechnologies/zeta-web/commit/f62b401fc723a5f76e0ede27b23c6fc672457002)) +* add version to storybook page footer ([12c265e](https://github.com/zebratechnologies/zeta-web/commit/12c265ec349cd4afc3b966cd29769cf2033e186b)) +* added "spreadGenerator" to fix bool attrs in storybook ([bf3c39e](https://github.com/zebratechnologies/zeta-web/commit/bf3c39ec40cc9d3e1bc63c742e0ff38a7396e1b4)) +* move types to individual components / mixins to improve documentation ([#99](https://github.com/zebratechnologies/zeta-web/issues/99)) ([99d4483](https://github.com/zebratechnologies/zeta-web/commit/99d4483736d1753c01b4d7953ef77caa8af841a9)) +* publish main storybook ([12c265e](https://github.com/zebratechnologies/zeta-web/commit/12c265ec349cd4afc3b966cd29769cf2033e186b)) +* style storybook ([#109](https://github.com/zebratechnologies/zeta-web/issues/109)) ([227b206](https://github.com/zebratechnologies/zeta-web/commit/227b206254c7161867f707b5461cf8174562264e)) +* update storybook args ([#112](https://github.com/zebratechnologies/zeta-web/issues/112)) ([03f023d](https://github.com/zebratechnologies/zeta-web/commit/03f023da8965d2be1973dbbc489845385f2b9dc0)) +* Updated storybook logo svgs ([bf3c39e](https://github.com/zebratechnologies/zeta-web/commit/bf3c39ec40cc9d3e1bc63c742e0ff38a7396e1b4)) + + +### ⛓️ Dependencies + +* Update zeta-icons library ([#102](https://github.com/zebratechnologies/zeta-web/issues/102)) ([bfe1857](https://github.com/zebratechnologies/zeta-web/commit/bfe1857434fb486404993818e9064cbf462a9b0a)) +* Update zeta-icons library ([#105](https://github.com/zebratechnologies/zeta-web/issues/105)) ([ba246a5](https://github.com/zebratechnologies/zeta-web/commit/ba246a516dbd60e5a14429c19eae3581803617d2)) +* Update zeta-icons library ([#113](https://github.com/zebratechnologies/zeta-web/issues/113)) ([e22958f](https://github.com/zebratechnologies/zeta-web/commit/e22958f543ff2ba02b8a31583c133ec8c9612255)) +* Update zeta-icons library ([#116](https://github.com/zebratechnologies/zeta-web/issues/116)) ([ca75da6](https://github.com/zebratechnologies/zeta-web/commit/ca75da6c69a84c92807517bee52d7bf779faa786)) + + +### 🧪 Tests + +* added /test to manually test importing in html ([bf3c39e](https://github.com/zebratechnologies/zeta-web/commit/bf3c39ec40cc9d3e1bc63c742e0ff38a7396e1b4)) + + +### 🧹 Miscellaneous Chores + +* **ci:** improved react-story generation ([bf3c39e](https://github.com/zebratechnologies/zeta-web/commit/bf3c39ec40cc9d3e1bc63c742e0ff38a7396e1b4)) +* **ci:** Update release-please.yml to run manually ([9f0c8dd](https://github.com/zebratechnologies/zeta-web/commit/9f0c8dd5f55a31c0e7a44cdc557f57d6dc7dbeff)) +* fix storybook logo to use paths not font ([#92](https://github.com/zebratechnologies/zeta-web/issues/92)) ([b4838e9](https://github.com/zebratechnologies/zeta-web/commit/b4838e99003d0f3fe7e7eccf40de5088b8c8bdde)) +* release 0.2.0 ([98f9d0a](https://github.com/zebratechnologies/zeta-web/commit/98f9d0ab137f3f60550c6c3ef09bfdf88cfd0afd)) +* removed all unsafeCSS calls ([bf3c39e](https://github.com/zebratechnologies/zeta-web/commit/bf3c39ec40cc9d3e1bc63c742e0ff38a7396e1b4)) +* Removed SCSS (changed to CSS) ([bf3c39e](https://github.com/zebratechnologies/zeta-web/commit/bf3c39ec40cc9d3e1bc63c742e0ff38a7396e1b4)) + + +### Continuous Integration + +* renamed the package to "@zebra-fed/zeta-web" ([bf3c39e](https://github.com/zebratechnologies/zeta-web/commit/bf3c39ec40cc9d3e1bc63c742e0ff38a7396e1b4)) + +## [0.1.0](https://github.com/zebratechnologies/zeta-web/compare/zeta-web-v0.0.1+1...zeta-web-v0.1.0) (2024-03-18) + + +### Features + +* added upload variant to progress circle ([#35](https://github.com/zebratechnologies/zeta-web/issues/35)) ([63e93da](https://github.com/zebratechnologies/zeta-web/commit/63e93da6165794691127f1acfc7967cb9c610e68)) +* avatar ([#51](https://github.com/zebratechnologies/zeta-web/issues/51)) ([6f1ac3c](https://github.com/zebratechnologies/zeta-web/commit/6f1ac3c7f544b04bd17493d2ea081ec175623919)) +* breadcrumb truncated ([#44](https://github.com/zebratechnologies/zeta-web/issues/44)) ([bb77f09](https://github.com/zebratechnologies/zeta-web/commit/bb77f0959422ab21b407ddfe8d8f19ff6b1b9b22)) +* Card header ([#55](https://github.com/zebratechnologies/zeta-web/issues/55)) ([2dd17e5](https://github.com/zebratechnologies/zeta-web/commit/2dd17e582a99c33adcf1ef5f55e8c62f6b23d7c2)) +* Created card component ([#57](https://github.com/zebratechnologies/zeta-web/issues/57)) ([e50e7a7](https://github.com/zebratechnologies/zeta-web/commit/e50e7a7ce0496c7ffe1bf2faab6405fb60911a88)) +* Created file upload ([#68](https://github.com/zebratechnologies/zeta-web/issues/68)) ([6dc0b98](https://github.com/zebratechnologies/zeta-web/commit/6dc0b9832340370baf2ccbec5df34bf6fd23a9c6)) +* Created list component ([#58](https://github.com/zebratechnologies/zeta-web/issues/58)) ([d8d1f49](https://github.com/zebratechnologies/zeta-web/commit/d8d1f492f7df7d0de35f0b0a5a3fb4627478f3b7)) +* Created navigation bar ([#59](https://github.com/zebratechnologies/zeta-web/issues/59)) ([4c2b1b1](https://github.com/zebratechnologies/zeta-web/commit/4c2b1b131f12218e7286b8569b3b3619a8a5ad97)) +* Created navigation drawer and popup mixin ([#65](https://github.com/zebratechnologies/zeta-web/issues/65)) ([6234e8e](https://github.com/zebratechnologies/zeta-web/commit/6234e8e1212cb6e85caaaafdc16c05506a17fc9d)) +* Created navigation drawer components ([#64](https://github.com/zebratechnologies/zeta-web/issues/64)) ([0f4a98b](https://github.com/zebratechnologies/zeta-web/commit/0f4a98bdeab342701e2e51f87345d8aaa9896e4b)) +* Created navigation drawer footer logo variant ([#67](https://github.com/zebratechnologies/zeta-web/issues/67)) ([a848e17](https://github.com/zebratechnologies/zeta-web/commit/a848e172e25c5b2f2092bc897522464b6e10b3b5)) +* Created navigation header and global header ([#71](https://github.com/zebratechnologies/zeta-web/issues/71)) ([4841d22](https://github.com/zebratechnologies/zeta-web/commit/4841d22624b96b7a284160d6785c85872b29f02e)) +* Created progress bar ([#73](https://github.com/zebratechnologies/zeta-web/issues/73)) ([6f637b2](https://github.com/zebratechnologies/zeta-web/commit/6f637b2f012b7622fe30500f7857d50d33f9086c)) +* Created slider ([#80](https://github.com/zebratechnologies/zeta-web/issues/80)) ([4be9423](https://github.com/zebratechnologies/zeta-web/commit/4be9423d60dca3950b10f77024eb51fe5b500104)) +* Dropdown menu item, Focusable Mixin, Radio button ([#49](https://github.com/zebratechnologies/zeta-web/issues/49)) ([8fd62f6](https://github.com/zebratechnologies/zeta-web/commit/8fd62f6e6bd52128ac6cd49e499178f457ecae1b)) +* Icon button ([#30](https://github.com/zebratechnologies/zeta-web/issues/30)) ([b732420](https://github.com/zebratechnologies/zeta-web/commit/b732420941fe2f2a217384a95a8d3a170d959f0e)) +* More menu ([#17](https://github.com/zebratechnologies/zeta-web/issues/17)) ([191a316](https://github.com/zebratechnologies/zeta-web/commit/191a31646e91bc5294098780faf061f03ccbc1aa)) +* pagination component ([#46](https://github.com/zebratechnologies/zeta-web/issues/46)) ([7c26850](https://github.com/zebratechnologies/zeta-web/commit/7c2685010b885d84837a6fdc365a0e23dac4ae8f)) +* password field ([#38](https://github.com/zebratechnologies/zeta-web/issues/38)) ([bd832b8](https://github.com/zebratechnologies/zeta-web/commit/bd832b80a9a463852ae76d80bde87bf6ee068414)) +* stepper input ([#48](https://github.com/zebratechnologies/zeta-web/issues/48)) ([131fd63](https://github.com/zebratechnologies/zeta-web/commit/131fd639b1d4210c57be3b257b6400f1e6fea0bb)) +* time input field & date input field ([#47](https://github.com/zebratechnologies/zeta-web/issues/47)) ([2299c97](https://github.com/zebratechnologies/zeta-web/commit/2299c972d41ac8d28ddc7342accdba8ab2304e4e)) +* Zeta Web Assist Chip ([#27](https://github.com/zebratechnologies/zeta-web/issues/27)) ([7b0c999](https://github.com/zebratechnologies/zeta-web/commit/7b0c999aec718e6380fd9f07b87dd77257b70dc6)) +* Zeta Web Filter Chip ([#24](https://github.com/zebratechnologies/zeta-web/issues/24)) ([d38bc45](https://github.com/zebratechnologies/zeta-web/commit/d38bc459deaa406d0041f8d9b5ea5ed9ec95bc42)) + + +### Bug Fixes + +* Accordion UX updates ([#53](https://github.com/zebratechnologies/zeta-web/issues/53)) ([54c28ed](https://github.com/zebratechnologies/zeta-web/commit/54c28ed3f91390745f27903aa9f13646748a8d4f)) +* component tests ([#45](https://github.com/zebratechnologies/zeta-web/issues/45)) ([b12267b](https://github.com/zebratechnologies/zeta-web/commit/b12267b88ef4d956b749a506f133ceed556e6401)) +* introduce release please manifest ([#42](https://github.com/zebratechnologies/zeta-web/issues/42)) ([16b673e](https://github.com/zebratechnologies/zeta-web/commit/16b673e58eb118004243f9692b5d5c10764ac415)) +* remove react from release please ([#87](https://github.com/zebratechnologies/zeta-web/issues/87)) ([621869b](https://github.com/zebratechnologies/zeta-web/commit/621869b40754d92ba790397e9383001d8fed0932)) +* Update icons action now saves to package json ([#66](https://github.com/zebratechnologies/zeta-web/issues/66)) ([673fb5f](https://github.com/zebratechnologies/zeta-web/commit/673fb5f1eb6ce9f7829329f39dfb9e0a95e80955)) +* update release please workflow to read the config files ([#41](https://github.com/zebratechnologies/zeta-web/issues/41)) ([51e576f](https://github.com/zebratechnologies/zeta-web/commit/51e576f365ceaf2fa92a9138b40e823fe70354e0)) +* Updating button themes to match new designs ([#56](https://github.com/zebratechnologies/zeta-web/issues/56)) ([265a96c](https://github.com/zebratechnologies/zeta-web/commit/265a96c411128ce0df9772e7f561c1849dfd6a86)) + + +### Miscellaneous Chores + +* release 0.1.0 ([#39](https://github.com/zebratechnologies/zeta-web/issues/39)) ([df73179](https://github.com/zebratechnologies/zeta-web/commit/df7317957b164a60d47a00251371da5509aac440)) diff --git a/CONTRIBUTING b/CONTRIBUTING new file mode 100644 index 0000000..1f43244 --- /dev/null +++ b/CONTRIBUTING @@ -0,0 +1,44 @@ +# Getting Involved + +Thank you for your interest in this project. We'd love to see your contributions. There are just few small guidelines you need to follow. +Please note we have a code of conduct, please follow it in all your interactions with the project. + +## Opening an issue + +If you've noticed a bug or you have a suggestion for a new feature, please go ahead and open an issue in this project. Please do include as much information as possible. + +Please file issues before doing substantial work; this will ensure that others don't duplicate the work and that there's a chance to discuss any design issues. + +## Making a code change + +We're always open to pull requests, but these should be small and clearly described so that we can understand what you're trying to do. + +When you're ready to start coding, fork the needed repository to your own GitHub account and make your changes in a new branch. Once you're happy, open a pull request and explain what the change is and why you think we should include it in our project. + +## Code reviews + +All submissions, including submissions by project members, require review. We use GitHub pull requests (PRs) for this purpose. Consult [GitHub Help](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/about-pull-requests) for more information on using pull requests. + +Before a PR can be reviewed, ensure you have done the following, and fixed any issues that may arise: + +- Ensure branch is up to date `git rebase main` +- Check formatting: `npm run lint` +- Run unit-tests: `npm run test` + + +## Documentation + +Our code is documented in-line with JSDoc according to the [Custom Elements Manifest](https://custom-elements-manifest.open-wc.org/analyzer/getting-started/#documenting-your-components). +All parameters, attributes and slots should be documented with brief descriptions and default values and classes should have thorough usage instructions. + +Once code is written, all components should have a story written in Storybook. Single components can use autodocs to create their documentation, but for complex components we may need to create the markdown. We must run `npm run analyze` before writing stories, as this updates [custom-elements.json](./custom-elements.json) which storybook uses. + +To create a story, first create a directory under `src/stories` for the component. + +In storybook, always add the `title` parameter as this is used in documentation. + +For single components, we should add `tags:['autodocs']` to storybook to generate the documentation. + +For complex components, we should create a folder within `src/stories` for this component. In this directory, we create all stories needed and add the same `title` to all. +Then create a `Docs.mdx` file in this folder, alongside all the stories. +Within this we need to import the stories, and the blocks from storybook - see [Badges](./src/stories/Badges/Docs.mdx) for an example. We can then display the canvas, description and ArgTypes for all the components. \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..373650e --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Zebra Technologies + +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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..d5a3683 --- /dev/null +++ b/README.md @@ -0,0 +1,127 @@ +<details class="repo-header"> + <summary>Zebra Repository Information</summary> + <ul> + <li> Zebra Business Unit : DMO - Innovation & Design</li> + <li> Zebra Manager : ncvt73 </li> + <li> Zebra Repo Admin: ncvt73 </li> + <li> Zebra Jira Project ID: UX </li> + <li> Product: Zebra Design System (Zeta) - Web Components Library </li> + <li> Topics: none </li> + </ul> +</details> + +<h1 class='sbdocs-title'>@zebra-fed/zeta-web</h1> + +Zeta Web is a native web component library created by Zebra Technologies written in TypeScript. +The Zeta Design System includes foundations, components, and best practices that can be used when building UX. + +> 🚧 **Note**: This package is in pre-release, and so many aspects are incomplete. + +## Previewing the components + +To view examples of all the components in the library, you can pull this repo and run the Storybook instance. + +You can also view the latest release at [Zeta](https://zeta-ds.web.app/) or the latest commits to main [here](https://zeta-web-main.web.app/). + +## How to Use + +Zeta Web Components can be directly used in many web frameworks. + +> ⚛️ **Note**: Using React? Zeta is not ready just yet. Whilst you wait, you can use [zds_react](https://www.npmjs.com/package/@zebra-fed/zds-react). + +1. Install `@zebra-fed/zeta-web` + + ```sh + # NPM + npm install git+https://github.com/zebratechnologies/zeta-web.git + # YARN + yarn add git+https://github.com/zebratechnologies/zeta-web.git + ``` + + <details> + <summary>🚧 <b>Note</b>: Public npm / yarn links coming soon.</summary> + + ```sh + # Future install instructions + # NPM + npm install @zebra-fed/zeta-web + # YARN + yarn add @zebra-fed/zeta-web + ``` + + </details> + +2. Import the desired Zeta Web Component into your app: + + ```js + /* Import the component in the JS/TS file where it is used */ + import "@zebra-fed/zeta-web/dist/components/button/button.js"; + + /* Import the Global Styles into the main App file */ + import "@zebra-fed/zeta-web/index.css"; + ``` + + or in HTML, + + ```html + <link + rel="stylesheet" + href="./node_modules/@zebra-fed/zeta-web/dist/style.css" + /> + <script + type="module" + src="./node_modules/@zebra-fed/zeta-web/dist/components/button/button.js" + ></script> + ``` + + You can also import the full package: + + ```js + import "@zebra-fed/zeta-web"; + ``` + +3. If you use any element that uses icons, you will also need to import the index.css from [@zebra-fed/zeta-web](https://www.npmjs.com/package/@zebra-fed/zeta-icons) + This is a temporary step for now. This will be automatically imported where needed in the future. + + ```html + <link + rel="stylesheet" + href="./node_modules/@zebra-fed/zeta-icons/dist/style.css" + /> + ``` + +4. Use the Web Component like any HTML element + + ```html + <zeta-button>Hello world!</zeta-button> + ``` + +## Developer Experience + +To improve the development experience while using the zeta web-components, the following packages can be useful: + +1. [`ts-lit-plugin`](https://www.npmjs.com/package/ts-lit-plugin) + + ts-lit-plugin adds type checking and code completion to lit-html. To install, first setup typescript in your project, then run: + + ```bash + # NPM + npm install ts-lit-plugin -D + + # Yarn + yarn add -D ts-lit-plugin + ``` + + and add the plugin to your tsconfig.json: + + ```json + { + "compilerOptions": { + "plugins": [ + { + "name": "ts-lit-plugin" + } + ] + } + } + ``` diff --git a/assets/zebra-logo-head.svg b/assets/zebra-logo-head.svg new file mode 100644 index 0000000..5b7ff70 --- /dev/null +++ b/assets/zebra-logo-head.svg @@ -0,0 +1,6 @@ +<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 72 80"> + <path fill="#1D1E23" + d="M30.58 24.16h13.73l-5.62-5.61-.02-.03h-7.22A9.28 9.28 0 0 0 30.58 0v18.55L15.16 3.17a27.39 27.39 0 0 0-4.8 3.17l14.59 14.54V52.8l11.27 11.27V56.1l-5.64-5.63V24.16Z" /> + <path fill="#1D1E23" + d="m36.22 24.2-.03 27.2h5.63l.03-21.55-5.63-5.66Zm13.7 5.66 5.63 5.63h-8.07l-5.63-5.63h8.07ZM19.31 47.5V23.26L6.37 10.32a28.06 28.06 0 0 0-3.18 4.8l10.49 10.47v7.97L.96 20.84c-.6 2.24-.94 4.6-.96 7.01L19.3 47.5Zm28.18-12 5.63 5.64v10.3H47.5V35.5Zm16.91 8.82h-5.64v7.13h-5.64a9.28 9.28 0 1 0 18.56 0l-7.29-7.13ZM.1 43.89v-7.97l36.12 36.11V80L.1 43.89Z" /> +</svg> \ No newline at end of file diff --git a/assets/zebra-logo.svg b/assets/zebra-logo.svg new file mode 100644 index 0000000..af39c43 --- /dev/null +++ b/assets/zebra-logo.svg @@ -0,0 +1,12 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 121 40"> + <defs> + <clipPath id="a" clipPathUnits="userSpaceOnUse"> + <path d="M0 792h1224V0H0Z"/> + </clipPath> + </defs> + <path d="M25.94 20.55 23.2 17.8v7.79h2.75Z"/> + <g clip-path="url(#a)" transform="matrix(.22217 0 0 -.22217 -75.5 107.79)"> + <path d="M407.08 429.98h30.17l-12.34 12.33-.05.06H409a20.4 20.4 0 0 1-1.92 40.7v-40.75l-33.9 33.79a60.64 60.64 0 0 1-10.56-6.97l32.08-31.95v-70.17l24.77-24.77v17.51l-12.39 12.39Z"/> + <path d="m419.47 429.9-.06-59.82h12.38l.06 47.38zm42.5-24.84-12.39 12.38h-17.73l12.37-12.38Zm-79.66-26.39v53.29l-28.45 28.45a61.71 61.71 0 0 1-6.98-10.55l23.04-23.04V409.3l-27.95 27.97a61.65 61.65 0 0 1-2.1-15.42s41.05-41.74 42.44-43.18m-42.23 25.46v-17.51l79.4-79.39v17.51zm141.32-18.48h-12.39v-15.66h-12.38a20.4 20.4 0 1 1 40.8 0zm100.88-15.92-9.3-17.67h-57.87l37.63 72.3h-33.32l9.3 17.67h57.88l-37.64-72.3Zm33.61 36.28h29.68v-17.67h-29.68v-18.61h29.68v-17.67h-51.53v89.97h51.53v-17.67h-29.68Zm62.24-38.31h7.96c9.17 0 13.09 3.1 13.09 10.4 0 4.3-1.62 7.41-4.86 9.17-2.43 1.34-4.59 1.61-11.2 1.61h-4.99Zm0 37.1h5.4c9.98 0 14.16 3.1 14.16 10.52 0 7.29-4.18 10.39-14.43 10.39h-5.13Zm8.9 37.23c11.47 0 18.08-1.22 22.94-4.32 6.2-3.77 9.98-11.46 9.98-20.1 0-6.33-2.03-11.6-6.07-15.64-2.57-2.7-4.59-3.92-9.44-5.26 5.53-1.22 7.95-2.43 10.79-5.13 4.04-3.77 6.07-9.04 6.07-15.65 0-8.36-3.38-15.78-9.18-19.69-4.45-2.97-9.84-4.18-19.01-4.18h-36.83v89.97zm71.62-42.89c8.9 0 14.3 4.58 14.3 12.4 0 7.42-5.27 12.55-12.95 12.55h-8.37v-24.95Zm-7.02-47.08h-21.85v89.97h29.14c11.87 0 20.5-2.16 26.43-6.74 6.2-4.86 9.72-12.95 9.72-22.8 0-13.76-5.8-22.8-17.4-27.38l19.82-33.05h-26.16l-19.7 34.8zm80.01 35.39 9.04 28.8 9.02-28.8Zm29.15-35.4h23.71l-29.54 89.98h-27.79l-29.67-89.97h23.04l5.55 17.7h29.16z"/> + </g> +</svg> diff --git a/catalog-info.yaml b/catalog-info.yaml new file mode 100644 index 0000000..81a03c0 --- /dev/null +++ b/catalog-info.yaml @@ -0,0 +1,29 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: zeta-web + title: Zeta Web Components Library + description: | + A library of UI Web components for the Zeta project. + This library is intended to be used by any Zebra web application to ensure a consistent look and feel by using UI components that implement the Zebra Design System User Experience. + links: + - url: https://design-zebra.web.app/web/storybook/ + title: Storybook + - url: https://design-zebra.web.app/web/tsdoc/ + title: TSDoc +# - url: https://npmjs.com/package/@zebra/zeta-web +# title: npm + tags: + - typescript + - web-components + annotations: + github.com/project-slug: zebratechnologies/zeta-web + github.com/team-slug: zebratechnologies/front-end-development +# backstage.io/techdocs-ref: dir:. +spec: + type: library + owner: group:DMO-FED + lifecycle: experimental + system: zeta + dependsOn: + - Resource:zeta-figma \ No newline at end of file diff --git a/custom-elements-manifest.config.js b/custom-elements-manifest.config.js new file mode 100644 index 0000000..e0dc4b8 --- /dev/null +++ b/custom-elements-manifest.config.js @@ -0,0 +1,16 @@ +import { customJSDocTagsPlugin } from "cem-plugin-custom-jsdoc-tags"; + +export default { + globs: ["src/components/**/*.ts", "src/mixins/**/*.ts"], + litelement: true, + plugins: [ + customJSDocTagsPlugin({ + tags: { + figma: { + isArray: true + }, + storybook: {} + } + }) + ] +}; diff --git a/demo/index.html b/demo/index.html new file mode 100644 index 0000000..fb620d7 --- /dev/null +++ b/demo/index.html @@ -0,0 +1,235 @@ +<html> + <head> + <link rel="stylesheet" href="../dist/style.css" /> + </head> + + <body> + <section> + <zeta-icon-button> check_circle </zeta-icon-button> + </section> + <section> + <span></span> + <span></span> + <h2 class="title">Sharp</h2> + <span></span> + <span></span> + <h2 class="title">Rounded</h2> + <span></span> + <span></span> + <span class="title">Large</span> + <span class="title">Medium</span> + <span class="title">Small</span> + <span class="title">Large</span> + <span class="title">Medium</span> + <span class="title">Small</span> + <span>Primary Enabled</span> + <zeta-button size="large"> Button </zeta-button> + <zeta-button size="medium"> Button </zeta-button> + <zeta-button size="small"> Button </zeta-button> + <zeta-button size="large" rounded> Button </zeta-button> + <zeta-button size="medium" rounded> Button </zeta-button> + <zeta-button size="small" rounded> Button </zeta-button> + <span>Primary Disabled</span> + <zeta-button size="large" disabled> Button </zeta-button> + <zeta-button size="medium" disabled> Button </zeta-button> + <zeta-button size="small" disabled> Button </zeta-button> + <zeta-button size="large" rounded disabled> Button </zeta-button> + <zeta-button size="medium" rounded disabled> Button </zeta-button> + <zeta-button size="small" rounded disabled> Button </zeta-button> + <span>Primary Variant Enabled</span> + <zeta-button flavor="primary-variant" size="large"> Button </zeta-button> + <zeta-button flavor="primary-variant" size="medium"> Button </zeta-button> + <zeta-button flavor="primary-variant" size="small"> Button </zeta-button> + <zeta-button flavor="primary-variant" size="large" rounded> + Button + </zeta-button> + <zeta-button flavor="primary-variant" size="medium" rounded> + Button + </zeta-button> + <zeta-button flavor="primary-variant" size="small" rounded> + Button + </zeta-button> + <span>Primary Variant Disabled</span> + <zeta-button flavor="primary-variant" size="large" disabled> + Button + </zeta-button> + <zeta-button flavor="primary-variant" size="medium" disabled> + Button + </zeta-button> + <zeta-button flavor="primary-variant" size="small" disabled> + Button + </zeta-button> + <zeta-button flavor="primary-variant" size="large" rounded disabled> + Button + </zeta-button> + <zeta-button flavor="primary-variant" size="medium" rounded disabled> + Button + </zeta-button> + <zeta-button flavor="primary-variant" size="small" rounded disabled> + Button + </zeta-button> + <span>Negative Enabled</span> + <zeta-button flavor="negative" size="large"> Button </zeta-button> + <zeta-button flavor="negative" size="medium"> Button </zeta-button> + <zeta-button flavor="negative" size="small"> Button </zeta-button> + <zeta-button flavor="negative" size="large" rounded> Button </zeta-button> + <zeta-button flavor="negative" size="medium" rounded> + Button + </zeta-button> + <zeta-button flavor="negative" size="small" rounded> Button </zeta-button> + <span>Negative Disabled</span> + <zeta-button flavor="negative" size="large" disabled> + Button + </zeta-button> + <zeta-button flavor="negative" size="medium" disabled> + Button + </zeta-button> + <zeta-button flavor="negative" size="small" disabled> + Button + </zeta-button> + <zeta-button flavor="negative" size="large" rounded disabled> + Button + </zeta-button> + <zeta-button flavor="negative" size="medium" rounded disabled> + Button + </zeta-button> + <zeta-button flavor="negative" size="small" rounded disabled> + Button + </zeta-button> + <span>Outline Enabled</span> + <zeta-button flavor="outline" size="large"> Button </zeta-button> + <zeta-button flavor="outline" size="medium"> Button </zeta-button> + <zeta-button flavor="outline" size="small"> Button </zeta-button> + <zeta-button flavor="outline" size="large" rounded> Button </zeta-button> + <zeta-button flavor="outline" size="medium" rounded> Button </zeta-button> + <zeta-button flavor="outline" size="small" rounded> Button </zeta-button> + <span>Outline Disabled</span> + <zeta-button flavor="outline" size="large" disabled> Button </zeta-button> + <zeta-button flavor="outline" size="medium" disabled> + Button + </zeta-button> + <zeta-button flavor="outline" size="small" disabled> Button </zeta-button> + <zeta-button flavor="outline" size="large" rounded disabled> + Button + </zeta-button> + <zeta-button flavor="outline" size="medium" rounded disabled> + Button + </zeta-button> + <zeta-button flavor="outline" size="small" rounded disabled> + Button + </zeta-button> + <span>Outline Subtle Enabled</span> + <zeta-button flavor="outline-subtle" size="large"> Button </zeta-button> + <zeta-button flavor="outline-subtle" size="medium"> Button </zeta-button> + <zeta-button flavor="outline-subtle" size="small"> Button </zeta-button> + <zeta-button flavor="outline-subtle" size="large" rounded> + Button + </zeta-button> + <zeta-button flavor="outline-subtle" size="medium" rounded> + Button + </zeta-button> + <zeta-button flavor="outline-subtle" size="small" rounded> + Button + </zeta-button> + <span>Outline Subtle Disabled</span> + <zeta-button flavor="outline-subtle" size="large" disabled> + Button + </zeta-button> + <zeta-button flavor="outline-subtle" size="medium" disabled> + Button + </zeta-button> + <zeta-button flavor="outline-subtle" size="small" disabled> + Button + </zeta-button> + <zeta-button flavor="outline-subtle" size="large" rounded disabled> + Button + </zeta-button> + <zeta-button flavor="outline-subtle" size="medium" rounded disabled> + Button + </zeta-button> + <zeta-button flavor="outline-subtle" size="small" rounded disabled> + Button + </zeta-button> + <span>Text Enabled</span> + <zeta-button flavor="text" size="large"> Button </zeta-button> + <zeta-button flavor="text" size="medium"> Button </zeta-button> + <zeta-button flavor="text" size="small"> Button </zeta-button> + <zeta-button flavor="text" size="large" rounded> Button </zeta-button> + <zeta-button flavor="text" size="medium" rounded> Button </zeta-button> + <zeta-button flavor="text" size="small" rounded> Button </zeta-button> + <span>Text Disabled</span> + <zeta-button flavor="text" size="large" disabled> Button </zeta-button> + <zeta-button flavor="text" size="medium" disabled> Button </zeta-button> + <zeta-button flavor="text" size="small" disabled> Button </zeta-button> + <zeta-button flavor="text" size="large" rounded disabled> + Button + </zeta-button> + <zeta-button flavor="text" size="medium" rounded disabled> + Button + </zeta-button> + <zeta-button flavor="text" size="small" rounded disabled> + Button + </zeta-button> + <span>Text Inverse Enabled</span> + <zeta-button flavor="text-inverse" size="large"> Button </zeta-button> + <zeta-button flavor="text-inverse" size="medium"> Button </zeta-button> + <zeta-button flavor="text-inverse" size="small"> Button </zeta-button> + <zeta-button flavor="text-inverse" size="large" rounded> + Button + </zeta-button> + <zeta-button flavor="text-inverse" size="medium" rounded> + Button + </zeta-button> + <zeta-button flavor="text-inverse" size="small" rounded> + Button + </zeta-button> + <span>Text Inverse Disabled</span> + <zeta-button flavor="text-inverse" size="large" disabled> + Button + </zeta-button> + <zeta-button flavor="text-inverse" size="medium" disabled> + Button + </zeta-button> + <zeta-button flavor="text-inverse" size="small" disabled> + Button + </zeta-button> + <zeta-button flavor="text-inverse" size="large" rounded disabled> + Button + </zeta-button> + <zeta-button flavor="text-inverse" size="medium" rounded disabled> + Button + </zeta-button> + <zeta-button flavor="text-inverse" size="small" rounded disabled> + Button + </zeta-button> + </section> + </body> + <script type="module" src="../dist/components/button/button.js"></script> + <script + type="module" + src="../dist/components/button/icon-button/icon-button.js" + ></script> + <!-- <script type="module" src="../dist/index.js"></script> --> + <link rel="preconnect" href="https://fonts.googleapis.com" /> + <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> + <link + href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@300;400;500&display=swap" + rel="stylesheet" + /> + + <style type="text/css"> + * { + font-family: "IBM Plex Sans", "Roboto", "Arial", sans-serif; + } + + section { + display: grid; + gap: 24px; + grid-template-columns: 150px repeat(2, 100px) 200px repeat(3, 100px); + } + + h2 { + margin: 0; + } + </style> +</html> diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..d67834b --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,96 @@ +import eslint from "@eslint/js"; +import tseslint from "typescript-eslint"; +import wc from "eslint-plugin-wc"; +import lit from "eslint-plugin-lit"; +import tsdoc from "eslint-plugin-tsdoc"; +import storybook from "eslint-plugin-storybook"; +import litA11Y from "eslint-plugin-lit-a11y"; +import prettier from "eslint-config-prettier"; + +export default tseslint.config( + eslint.configs.recommended, + ...tseslint.configs.recommendedTypeChecked, + prettier, + storybook.configs["flat/recommended"], + wc.configs["flat/recommended"], + lit.configs["flat/recommended"], + { + ignores: ["**/*.stories.*", "**/generated"], + }, + { + files: ["src/**/*.ts"], + plugins: { + tsdoc: tsdoc, + "lit-a11y": litA11Y, + }, + + languageOptions: { + parser: tseslint.parser, + ecmaVersion: "latest", + sourceType: "module", + + parserOptions: { + project: ["./tsconfig.json"], + }, + }, + + settings: {}, + + rules: { + "wc/attach-shadow-constructor": "error", + "wc/guard-super-call": "error", + "wc/no-closed-shadow-root": "error", + "wc/no-constructor-attributes": "error", + "wc/no-constructor-params": "error", + "wc/no-invalid-element-name": "error", + "wc/no-self-class": "error", + "wc/no-typos": "error", + "wc/require-listener-teardown": "error", + camelcase: 1, + "comma-dangle": [1, "never"], + "eol-last": [0], + + indent: [ + "off", + 2, + { + SwitchCase: 1, + }, + ], + + "no-extra-semi": "warn", + "no-extra-boolean-cast": "warn", + "no-undef": 0, + "no-unused-vars": "off", + "@typescript-eslint/consistent-type-imports": [ + "error", + { + prefer: "type-imports", + disallowTypeAnnotations: true, + fixStyle: "separate-type-imports", + }, + ], + + "@typescript-eslint/no-unused-vars": [ + "warn", + { + argsIgnorePattern: "^_", + varsIgnorePattern: "^_", + }, + ], + + semi: [1, "always"], + "spaced-comment": 0, + }, + }, + { + files: ["src/**/*.test.ts"], + rules: { + "no-unused-expressions": 0, + "@typescript-eslint/no-unused-expressions": 0, + "@typescript-eslint/no-explicit-any": 0, + "@typescript-eslint/no-unsafe-assignment": 0, + "@typescript-eslint/no-unsafe-member-access": 0, + }, + } +); diff --git a/firebase.json b/firebase.json new file mode 100644 index 0000000..1341f77 --- /dev/null +++ b/firebase.json @@ -0,0 +1,17 @@ +{ + "hosting": { + "public": "storybook-static", + "ignore": [ + "firebase.json", + "**/.*", + "**/node_modules/**" + ], + "target": "web", + "rewrites": [ + { + "source": "**", + "destination": "/index.html" + } + ] + } +} diff --git a/lit-localize.json b/lit-localize.json new file mode 100644 index 0000000..3deb1de --- /dev/null +++ b/lit-localize.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://raw.githubusercontent.com/lit/lit/main/packages/localize-tools/config.schema.json", + "sourceLocale": "en", + "targetLocales": ["en-GB", "en-US", "fr", "it", "de", "es"], + "tsConfig": "./tsconfig.json", + "output": { + "mode": "runtime", + "outputDir": "./src/generated/locales" + }, + "interchange": { + "format": "xliff", + "xliffDir": "./locales/" + } +} diff --git a/locales/de.xlf b/locales/de.xlf new file mode 100644 index 0000000..f4e5912 --- /dev/null +++ b/locales/de.xlf @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2"> +<file target-language="de" source-language="en" original="lit-localize-inputs" datatype="plaintext"> +<body> +<trans-unit id="se6ce0e7d35ca8a05"> + <source>Drop files here to upload</source> +</trans-unit> +<trans-unit id="sa192dac6f544578d"> + <source>Selection contains files with invalid types.</source> +</trans-unit> +<trans-unit id="s7008dc713aed8339"> + <source>Multiple files are not allowed.</source> +</trans-unit> +<trans-unit id="s08b05407b5565ca4"> + <source>or</source> +</trans-unit> +</body> +</file> +</xliff> diff --git a/locales/en-GB.xlf b/locales/en-GB.xlf new file mode 100644 index 0000000..969bf1f --- /dev/null +++ b/locales/en-GB.xlf @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2"> +<file target-language="en-GB" source-language="en" original="lit-localize-inputs" datatype="plaintext"> +<body> +<trans-unit id="se6ce0e7d35ca8a05"> + <source>Drop files here to upload</source> +</trans-unit> +<trans-unit id="sa192dac6f544578d"> + <source>Selection contains files with invalid types.</source> +</trans-unit> +<trans-unit id="s7008dc713aed8339"> + <source>Multiple files are not allowed.</source> +</trans-unit> +<trans-unit id="s08b05407b5565ca4"> + <source>or</source> +</trans-unit> +</body> +</file> +</xliff> diff --git a/locales/en-US.xlf b/locales/en-US.xlf new file mode 100644 index 0000000..4fe2117 --- /dev/null +++ b/locales/en-US.xlf @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2"> +<file target-language="en-US" source-language="en" original="lit-localize-inputs" datatype="plaintext"> +<body> +<trans-unit id="se6ce0e7d35ca8a05"> + <source>Drop files here to upload</source> +</trans-unit> +<trans-unit id="sa192dac6f544578d"> + <source>Selection contains files with invalid types.</source> +</trans-unit> +<trans-unit id="s7008dc713aed8339"> + <source>Multiple files are not allowed.</source> +</trans-unit> +<trans-unit id="s08b05407b5565ca4"> + <source>or</source> +</trans-unit> +</body> +</file> +</xliff> diff --git a/locales/es.xlf b/locales/es.xlf new file mode 100644 index 0000000..be7cd44 --- /dev/null +++ b/locales/es.xlf @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2"> +<file target-language="es" source-language="en" original="lit-localize-inputs" datatype="plaintext"> +<body> +<trans-unit id="se6ce0e7d35ca8a05"> + <source>Drop files here to upload</source> +</trans-unit> +<trans-unit id="sa192dac6f544578d"> + <source>Selection contains files with invalid types.</source> +</trans-unit> +<trans-unit id="s7008dc713aed8339"> + <source>Multiple files are not allowed.</source> +</trans-unit> +<trans-unit id="s08b05407b5565ca4"> + <source>or</source> +</trans-unit> +</body> +</file> +</xliff> diff --git a/locales/fr.xlf b/locales/fr.xlf new file mode 100644 index 0000000..3cf0432 --- /dev/null +++ b/locales/fr.xlf @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2"> +<file target-language="fr" source-language="en" original="lit-localize-inputs" datatype="plaintext"> +<body> +<trans-unit id="se6ce0e7d35ca8a05"> + <source>Drop files here to upload</source> +</trans-unit> +<trans-unit id="sa192dac6f544578d"> + <source>Selection contains files with invalid types.</source> +</trans-unit> +<trans-unit id="s7008dc713aed8339"> + <source>Multiple files are not allowed.</source> +</trans-unit> +<trans-unit id="s08b05407b5565ca4"> + <source>or</source> +</trans-unit> +</body> +</file> +</xliff> diff --git a/locales/it.xlf b/locales/it.xlf new file mode 100644 index 0000000..590ca6a --- /dev/null +++ b/locales/it.xlf @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2"> +<file target-language="it" source-language="en" original="lit-localize-inputs" datatype="plaintext"> +<body> +<trans-unit id="se6ce0e7d35ca8a05"> + <source>Drop files here to upload</source> +</trans-unit> +<trans-unit id="sa192dac6f544578d"> + <source>Selection contains files with invalid types.</source> +</trans-unit> +<trans-unit id="s7008dc713aed8339"> + <source>Multiple files are not allowed.</source> +</trans-unit> +<trans-unit id="s08b05407b5565ca4"> + <source>or</source> +</trans-unit> +</body> +</file> +</xliff> diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..c77a103 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,15076 @@ +{ + "name": "@zebra-fed/zeta-web", + "version": "0.2.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@zebra-fed/zeta-web", + "version": "0.2.0", + "license": "MIT", + "dependencies": { + "@fontsource/ibm-plex-sans": "^5.0.21", + "@lit/localize": "^0.12.2", + "@zebra-fed/zeta-icons": "^0.9.0", + "lit": "^3.2.1" + }, + "devDependencies": { + "@actions/core": "^1.11.1", + "@custom-elements-manifest/analyzer": "^0.10.3", + "@eslint/js": "^9.15.0", + "@etchteam/storybook-addon-status": "^5.0.0", + "@lit/localize-tools": "^0.8.0", + "@open-wc/lit-helpers": "^0.7.0", + "@open-wc/testing": "^4.0.0", + "@remcovaes/web-test-runner-vite-plugin": "^1.2.1", + "@storybook/addon-a11y": "^8.4.4", + "@storybook/addon-designs": "^8.0.3", + "@storybook/addon-docs": "^8.4.6", + "@storybook/addon-essentials": "^8.4.4", + "@storybook/addon-links": "^8.4.4", + "@storybook/blocks": "^8.4.4", + "@storybook/manager-api": "^8.4.6", + "@storybook/test": "^8.4.4", + "@storybook/test-runner": "^0.20.1", + "@storybook/theming": "^8.4.4", + "@storybook/web-components": "^8.4.4", + "@storybook/web-components-vite": "^8.4.4", + "@types/jest": "^29.5.14", + "@types/node": "^22.10.1", + "@typescript-eslint/eslint-plugin": "^8.17.0", + "@typescript-eslint/parser": "^8.17.0", + "@web/dev-server": "^0.4.6", + "@web/test-runner": "^0.19.0", + "@web/test-runner-commands": "^0.9.0", + "@web/test-runner-playwright": "^0.11.0", + "axe-playwright": "^2.0.2", + "cem-plugin-better-lit-types": "^0.2.1", + "cem-plugin-custom-jsdoc-tags": "^1.1.2", + "command-line-args": "^6.0.1", + "del": "^8.0.0", + "esbuild": "0.24.0", + "eslint": "^9.15.0", + "eslint-config-prettier": "^9.1.0", + "eslint-formatter-unix": "^8.40.0", + "eslint-plugin-lit": "^1.15.0", + "eslint-plugin-lit-a11y": "^4.1.4", + "eslint-plugin-storybook": "^0.11.1", + "eslint-plugin-tsdoc": "^0.4.0", + "eslint-plugin-wc": "^2.2.0", + "lit-analyzer": "^2.0.3", + "npm-run-all": "^4.1.5", + "prettier": "3.4.1", + "react": "^18.3.1", + "sinon": "^19.0.2", + "storybook": "^8.4.4", + "ts-lit-plugin": "^2.0.2", + "ts-morph": "^24.0.0", + "typescript": "^5.7.2", + "typescript-eslint": "^8.16.0", + "vite": "^6.0.2" + } + }, + "node_modules/@actions/core": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@actions/core/-/core-1.11.1.tgz", + "integrity": "sha512-hXJCSrkwfA46Vd9Z3q4cpEpHB1rL5NG04+/rbqW9d3+CSvtB1tYe8UTpAlixa1vj0m/ULglfEK2UKxMGxCxv5A==", + "dev": true, + "dependencies": { + "@actions/exec": "^1.1.1", + "@actions/http-client": "^2.0.1" + } + }, + "node_modules/@actions/exec": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.1.1.tgz", + "integrity": "sha512-+sCcHHbVdk93a0XT19ECtO/gIXoxvdsgQLzb2fE2/5sIZmWQuluYyjPQtrtTHdU1YzTZ7bAPN4sITq2xi1679w==", + "dev": true, + "dependencies": { + "@actions/io": "^1.0.1" + } + }, + "node_modules/@actions/http-client": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.2.3.tgz", + "integrity": "sha512-mx8hyJi/hjFvbPokCg4uRd4ZX78t+YyRPtnKWwIl+RzNaVuFpQHfmlGVfsKEJN8LwTCvL+DfVgAM04XaHkm6bA==", + "dev": true, + "dependencies": { + "tunnel": "^0.0.6", + "undici": "^5.25.4" + } + }, + "node_modules/@actions/io": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@actions/io/-/io-1.1.3.tgz", + "integrity": "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q==", + "dev": true + }, + "node_modules/@adobe/css-tools": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.1.tgz", + "integrity": "sha512-12WGKBQzjUAI4ayyF4IAtfw2QR/IDoqk6jTddXDhtYTJF9ASmoE1zst7cVtP0aL/F1jUJL5r+JxKXKEgHNbEUQ==", + "dev": true + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", + "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.25.9", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.2.tgz", + "integrity": "sha512-Z0WgzSEa+aUcdiJuCIqgujCshpMWgUpgOxXotrYPSA53hA3qopNaqcJpyr0hVb1FeWdnqFA35/fUtXgBK8srQg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.0.tgz", + "integrity": "sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.26.0", + "@babel/generator": "^7.26.0", + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helpers": "^7.26.0", + "@babel/parser": "^7.26.0", + "@babel/template": "^7.25.9", + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.26.0", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.2.tgz", + "integrity": "sha512-zevQbhbau95nkoxSq3f/DC/SC+EEOUZd3DYqfSkMhY2/wfSeaHV1Ew4vk8e+x8lja31IbyuUa2uQ3JONqKbysw==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.26.2", + "@babel/types": "^7.26.0", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.9.tgz", + "integrity": "sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.25.9", + "@babel/helper-validator-option": "^7.25.9", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", + "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", + "dev": true, + "dependencies": { + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz", + "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9", + "@babel/traverse": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.9.tgz", + "integrity": "sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz", + "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.0.tgz", + "integrity": "sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==", + "dev": true, + "dependencies": { + "@babel/template": "^7.25.9", + "@babel/types": "^7.26.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.2.tgz", + "integrity": "sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==", + "dev": true, + "dependencies": { + "@babel/types": "^7.26.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.26.0.tgz", + "integrity": "sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.25.9.tgz", + "integrity": "sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.9.tgz", + "integrity": "sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz", + "integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==", + "dev": true, + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz", + "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.25.9", + "@babel/parser": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.9.tgz", + "integrity": "sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.25.9", + "@babel/generator": "^7.25.9", + "@babel/parser": "^7.25.9", + "@babel/template": "^7.25.9", + "@babel/types": "^7.25.9", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.0.tgz", + "integrity": "sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "optional": true, + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "optional": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@custom-elements-manifest/analyzer": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@custom-elements-manifest/analyzer/-/analyzer-0.10.3.tgz", + "integrity": "sha512-e2Ax59vK9sNedmDlPqZS11L54iAlKSjOJuv5etpTy5SygLBW3GcUtocHZm8wO013L0griTPpgWB0tuV7/JXy5A==", + "dev": true, + "dependencies": { + "@custom-elements-manifest/find-dependencies": "^0.0.5", + "@github/catalyst": "^1.6.0", + "@web/config-loader": "0.1.3", + "chokidar": "3.5.2", + "command-line-args": "5.1.2", + "comment-parser": "1.2.4", + "custom-elements-manifest": "1.0.0", + "debounce": "1.2.1", + "globby": "11.0.4", + "typescript": "~5.4.2" + }, + "bin": { + "cem": "cem.js", + "custom-elements-manifest": "cem.js" + } + }, + "node_modules/@custom-elements-manifest/analyzer/node_modules/command-line-args": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/command-line-args/-/command-line-args-5.1.2.tgz", + "integrity": "sha512-fytTsbndLbl+pPWtS0CxLV3BEWw9wJayB8NnU2cbQqVPsNdYezQeT+uIQv009m+GShnMNyuoBrRo8DTmuTfSCA==", + "dev": true, + "dependencies": { + "array-back": "^6.1.2", + "find-replace": "^3.0.0", + "lodash.camelcase": "^4.3.0", + "typical": "^4.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/@custom-elements-manifest/analyzer/node_modules/find-replace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-replace/-/find-replace-3.0.0.tgz", + "integrity": "sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ==", + "dev": true, + "dependencies": { + "array-back": "^3.0.1" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/@custom-elements-manifest/analyzer/node_modules/find-replace/node_modules/array-back": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-3.1.0.tgz", + "integrity": "sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/@custom-elements-manifest/analyzer/node_modules/typescript": { + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/@custom-elements-manifest/analyzer/node_modules/typical": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/typical/-/typical-4.0.0.tgz", + "integrity": "sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@custom-elements-manifest/find-dependencies": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/@custom-elements-manifest/find-dependencies/-/find-dependencies-0.0.5.tgz", + "integrity": "sha512-fKIMMZCDFSoL2ySUoz8knWgpV4jpb0lUXgLOvdZQMQFHxgxz1PqOJpUIypwvEVyKk3nEHRY4f10gNol02HjeCg==", + "dev": true, + "dependencies": { + "es-module-lexer": "^0.9.3" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.0.tgz", + "integrity": "sha512-WtKdFM7ls47zkKHFVzMz8opM7LkcsIp9amDUBIAWirg70RM71WRSjdILPsY5Uv1D42ZpUfaPILDlfactHgsRkw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.24.0.tgz", + "integrity": "sha512-arAtTPo76fJ/ICkXWetLCc9EwEHKaeya4vMrReVlEIUCAUncH7M4bhMQ+M9Vf+FFOZJdTNMXNBrWwW+OXWpSew==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.24.0.tgz", + "integrity": "sha512-Vsm497xFM7tTIPYK9bNTYJyF/lsP590Qc1WxJdlB6ljCbdZKU9SY8i7+Iin4kyhV/KV5J2rOKsBQbB77Ab7L/w==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.24.0.tgz", + "integrity": "sha512-t8GrvnFkiIY7pa7mMgJd7p8p8qqYIz1NYiAoKc75Zyv73L3DZW++oYMSHPRarcotTKuSs6m3hTOa5CKHaS02TQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.24.0.tgz", + "integrity": "sha512-CKyDpRbK1hXwv79soeTJNHb5EiG6ct3efd/FTPdzOWdbZZfGhpbcqIpiD0+vwmpu0wTIL97ZRPZu8vUt46nBSw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.24.0.tgz", + "integrity": "sha512-rgtz6flkVkh58od4PwTRqxbKH9cOjaXCMZgWD905JOzjFKW+7EiUObfd/Kav+A6Gyud6WZk9w+xu6QLytdi2OA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.0.tgz", + "integrity": "sha512-6Mtdq5nHggwfDNLAHkPlyLBpE5L6hwsuXZX8XNmHno9JuL2+bg2BX5tRkwjyfn6sKbxZTq68suOjgWqCicvPXA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.24.0.tgz", + "integrity": "sha512-D3H+xh3/zphoX8ck4S2RxKR6gHlHDXXzOf6f/9dbFt/NRBDIE33+cVa49Kil4WUjxMGW0ZIYBYtaGCa2+OsQwQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.24.0.tgz", + "integrity": "sha512-gJKIi2IjRo5G6Glxb8d3DzYXlxdEj2NlkixPsqePSZMhLudqPhtZ4BUrpIuTjJYXxvF9njql+vRjB2oaC9XpBw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.24.0.tgz", + "integrity": "sha512-TDijPXTOeE3eaMkRYpcy3LarIg13dS9wWHRdwYRnzlwlA370rNdZqbcp0WTyyV/k2zSxfko52+C7jU5F9Tfj1g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.24.0.tgz", + "integrity": "sha512-K40ip1LAcA0byL05TbCQ4yJ4swvnbzHscRmUilrmP9Am7//0UjPreh4lpYzvThT2Quw66MhjG//20mrufm40mA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.24.0.tgz", + "integrity": "sha512-0mswrYP/9ai+CU0BzBfPMZ8RVm3RGAN/lmOMgW4aFUSOQBjA31UP8Mr6DDhWSuMwj7jaWOT0p0WoZ6jeHhrD7g==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.24.0.tgz", + "integrity": "sha512-hIKvXm0/3w/5+RDtCJeXqMZGkI2s4oMUGj3/jM0QzhgIASWrGO5/RlzAzm5nNh/awHE0A19h/CvHQe6FaBNrRA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.24.0.tgz", + "integrity": "sha512-HcZh5BNq0aC52UoocJxaKORfFODWXZxtBaaZNuN3PUX3MoDsChsZqopzi5UupRhPHSEHotoiptqikjN/B77mYQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.24.0.tgz", + "integrity": "sha512-bEh7dMn/h3QxeR2KTy1DUszQjUrIHPZKyO6aN1X4BCnhfYhuQqedHaa5MxSQA/06j3GpiIlFGSsy1c7Gf9padw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.24.0.tgz", + "integrity": "sha512-ZcQ6+qRkw1UcZGPyrCiHHkmBaj9SiCD8Oqd556HldP+QlpUIe2Wgn3ehQGVoPOvZvtHm8HPx+bH20c9pvbkX3g==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.24.0.tgz", + "integrity": "sha512-vbutsFqQ+foy3wSSbmjBXXIJ6PL3scghJoM8zCL142cGaZKAdCZHyf+Bpu/MmX9zT9Q0zFBVKb36Ma5Fzfa8xA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.24.0.tgz", + "integrity": "sha512-hjQ0R/ulkO8fCYFsG0FZoH+pWgTTDreqpqY7UnQntnaKv95uP5iW3+dChxnx7C3trQQU40S+OgWhUVwCjVFLvg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.0.tgz", + "integrity": "sha512-MD9uzzkPQbYehwcN583yx3Tu5M8EIoTD+tUgKF982WYL9Pf5rKy9ltgD0eUgs8pvKnmizxjXZyLt0z6DC3rRXg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.24.0.tgz", + "integrity": "sha512-4ir0aY1NGUhIC1hdoCzr1+5b43mw99uNwVzhIq1OY3QcEwPDO3B7WNXBzaKY5Nsf1+N11i1eOfFcq+D/gOS15Q==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.24.0.tgz", + "integrity": "sha512-jVzdzsbM5xrotH+W5f1s+JtUy1UWgjU0Cf4wMvffTB8m6wP5/kx0KiaLHlbJO+dMgtxKV8RQ/JvtlFcdZ1zCPA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.24.0.tgz", + "integrity": "sha512-iKc8GAslzRpBytO2/aN3d2yb2z8XTVfNV0PjGlCxKo5SgWmNXx82I/Q3aG1tFfS+A2igVCY97TJ8tnYwpUWLCA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.24.0.tgz", + "integrity": "sha512-vQW36KZolfIudCcTnaTpmLQ24Ha1RjygBo39/aLkM2kmjkWmZGEJ5Gn9l5/7tzXA42QGIoWbICfg6KLLkIw6yw==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.24.0.tgz", + "integrity": "sha512-7IAFPrjSQIJrGsK6flwg7NFmwBoSTyF3rl7If0hNUFQU4ilTsEPL6GuMuU9BfIWVVGuRnuIidkSMC+c0Otu8IA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz", + "integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.0.tgz", + "integrity": "sha512-zdHg2FPIFNKPdcHWtiNT+jEFCHYVplAXRDlQDyqy0zGx/q2parwh7brGJSiTxRk/TSMkbM//zt/f5CHgyTyaSQ==", + "dev": true, + "dependencies": { + "@eslint/object-schema": "^2.1.4", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-array/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/core": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.9.0.tgz", + "integrity": "sha512-7ATR9F0e4W85D/0w7cU0SNj7qkAexMG+bAHEZOjo9akvGuhHE2m7umzWzfnpa0XAg5Kxc1BWmtPMV67jJ+9VUg==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.2.0.tgz", + "integrity": "sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/js": { + "version": "9.16.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.16.0.tgz", + "integrity": "sha512-tw2HxzQkrbeuvyj1tG2Yqq+0H9wGoI2IMk4EOsQeX+vmd75FtJAzf+gTA69WF+baUKRYQ3x2kbLE08js5OsTVg==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.4.tgz", + "integrity": "sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.3.tgz", + "integrity": "sha512-2b/g5hRmpbb1o4GnTZax9N9m0FXzz9OV42ZzI4rDDMDuHUqigAiQCEWChBWCY4ztAGVRjoWT19v0yMmc5/L5kA==", + "dev": true, + "dependencies": { + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@esm-bundle/chai": { + "version": "4.3.4-fix.0", + "resolved": "https://registry.npmjs.org/@esm-bundle/chai/-/chai-4.3.4-fix.0.tgz", + "integrity": "sha512-26SKdM4uvDWlY8/OOOxSB1AqQWeBosCX3wRYUZO7enTAj03CtVxIiCimYVG2WpULcyV51qapK4qTovwkUr5Mlw==", + "dev": true, + "dependencies": { + "@types/chai": "^4.2.12" + } + }, + "node_modules/@etchteam/storybook-addon-status": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@etchteam/storybook-addon-status/-/storybook-addon-status-5.0.0.tgz", + "integrity": "sha512-0Dg94xQ3T+rYTi6ekFkaH0XU9k0IQOqidiSmwN47Oqw4u1qjxK6m7wV3j7C0deLEuf4U4PrAcsDfDjyehKne2A==", + "dev": true, + "dependencies": { + "lodash": "^4.17.21" + } + }, + "node_modules/@fastify/busboy": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", + "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", + "dev": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@figspec/components": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@figspec/components/-/components-1.0.3.tgz", + "integrity": "sha512-fBwHzJ4ouuOUJEi+yBZIrOy+0/fAjB3AeTcIHTT1PRxLz8P63xwC7R0EsIJXhScIcc+PljGmqbbVJCjLsnaGYA==", + "dev": true, + "dependencies": { + "lit": "^2.1.3" + } + }, + "node_modules/@figspec/components/node_modules/@lit/reactive-element": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-1.6.3.tgz", + "integrity": "sha512-QuTgnG52Poic7uM1AN5yJ09QMe0O28e10XzSvWDz02TJiiKee4stsiownEIadWm8nYzyDAyT+gKzUoZmiWQtsQ==", + "dev": true, + "dependencies": { + "@lit-labs/ssr-dom-shim": "^1.0.0" + } + }, + "node_modules/@figspec/components/node_modules/lit": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/lit/-/lit-2.8.0.tgz", + "integrity": "sha512-4Sc3OFX9QHOJaHbmTMk28SYgVxLN3ePDjg7hofEft2zWlehFL3LiAuapWc4U/kYwMYJSh2hTCPZ6/LIC7ii0MA==", + "dev": true, + "dependencies": { + "@lit/reactive-element": "^1.6.0", + "lit-element": "^3.3.0", + "lit-html": "^2.8.0" + } + }, + "node_modules/@figspec/components/node_modules/lit-element": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-3.3.3.tgz", + "integrity": "sha512-XbeRxmTHubXENkV4h8RIPyr8lXc+Ff28rkcQzw3G6up2xg5E8Zu1IgOWIwBLEQsu3cOVFqdYwiVi0hv0SlpqUA==", + "dev": true, + "dependencies": { + "@lit-labs/ssr-dom-shim": "^1.1.0", + "@lit/reactive-element": "^1.3.0", + "lit-html": "^2.8.0" + } + }, + "node_modules/@figspec/components/node_modules/lit-html": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-2.8.0.tgz", + "integrity": "sha512-o9t+MQM3P4y7M7yNzqAyjp7z+mQGa4NS4CxiyLqFPyFWyc4O+nodLrkrxSaCTrla6M5YOLaT3RpbbqjszB5g3Q==", + "dev": true, + "dependencies": { + "@types/trusted-types": "^2.0.2" + } + }, + "node_modules/@figspec/react": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@figspec/react/-/react-1.0.3.tgz", + "integrity": "sha512-r683qOko+5CbT48Ox280fMx2MNAtaFPgCNJvldOqN3YtmAzlcTT+YSxd3OahA+kjXGGrnzDbUgeTOX1cPLII+g==", + "dev": true, + "dependencies": { + "@figspec/components": "^1.0.1", + "@lit-labs/react": "^1.0.2" + }, + "peerDependencies": { + "react": "^16.14.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/@fontsource/ibm-plex-sans": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@fontsource/ibm-plex-sans/-/ibm-plex-sans-5.1.0.tgz", + "integrity": "sha512-v2aFHGh33ogG+At6dVNUCX6vWlNAhQ6STWj5WrBKPxVWX1SsAnHNq8sXQBa7WHEt29Irmozuk7GTp6GzFlpwdQ==" + }, + "node_modules/@github/catalyst": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@github/catalyst/-/catalyst-1.7.0.tgz", + "integrity": "sha512-qOAxrDdRZz9+v4y2WoAfh11rpRY/x4FRofPNmJyZFzAjubtzE3sCa/tAycWWufmQGoYiwwzL/qJBBgyg7avxPw==", + "dev": true + }, + "node_modules/@hapi/bourne": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@hapi/bourne/-/bourne-3.0.0.tgz", + "integrity": "sha512-Waj1cwPXJDucOib4a3bAISsKJVb15MKi9IvmTI/7ssVEm6sywXGjVJDhl6/umt1pK1ZS7PacXU3A1PmFKHEZ2w==", + "dev": true + }, + "node_modules/@hapi/hoek": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", + "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==", + "dev": true + }, + "node_modules/@hapi/topo": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", + "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", + "dev": true, + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.6", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", + "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", + "dev": true, + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.3.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "dev": true, + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.1.tgz", + "integrity": "sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==", + "dev": true, + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", + "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/console/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/core": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", + "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", + "dev": true, + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/reporters": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.7.0", + "jest-config": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-resolve-dependencies": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "jest-watcher": "^29.7.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/core/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/core/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/core/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true + }, + "node_modules/@jest/core/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/create-cache-key-function": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/create-cache-key-function/-/create-cache-key-function-29.7.0.tgz", + "integrity": "sha512-4QqS3LY5PBmTRHj9sAg1HLoPzqAI0uOX6wI/TRqHIcOxlFidy6YEmCQJk6FSZjNLGCeubDMfmkWL+qaLKhSGQA==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/environment": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "dev": true, + "dependencies": { + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", + "dev": true, + "dependencies": { + "expect": "^29.7.0", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "dev": true, + "dependencies": { + "jest-get-type": "^29.6.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", + "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/types": "^29.6.3", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", + "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", + "dev": true, + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/reporters/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", + "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.18", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", + "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", + "dev": true, + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", + "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", + "dev": true, + "dependencies": { + "@jest/test-result": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-sequencer/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@lit-labs/react": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@lit-labs/react/-/react-1.2.1.tgz", + "integrity": "sha512-DiZdJYFU0tBbdQkfwwRSwYyI/mcWkg3sWesKRsHUd4G+NekTmmeq9fzsurvcKTNVa0comNljwtg4Hvi1ds3V+A==", + "dev": true + }, + "node_modules/@lit-labs/ssr-dom-shim": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.2.1.tgz", + "integrity": "sha512-wx4aBmgeGvFmOKucFKY+8VFJSYZxs9poN3SDNQFF6lT6NrQUnHiPB2PWz2sc4ieEcAaYYzN+1uWahEeTq2aRIQ==" + }, + "node_modules/@lit/localize": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/@lit/localize/-/localize-0.12.2.tgz", + "integrity": "sha512-Qv9kvgJKDq/JVSwXOxuWvQnnOBysHA99ti9im9a4fImCmx+fto+XXcUYQbjZHqiueEEc4V20PcRDPO+1g/6seQ==", + "dependencies": { + "lit": "^3.2.0" + } + }, + "node_modules/@lit/localize-tools": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@lit/localize-tools/-/localize-tools-0.8.0.tgz", + "integrity": "sha512-18HY8Ln8ZhSW2CYlXY9+CgDeEoxIY4dBvJ7W5fOe5262NvWEQghMgEC01WT9SQm4Non9C42aDGEYLEegw2ZBhw==", + "dev": true, + "dependencies": { + "@lit/localize": "^0.12.0", + "@parse5/tools": "^0.3.0", + "@xmldom/xmldom": "^0.8.2", + "fast-glob": "^3.2.7", + "fs-extra": "^10.0.0", + "jsonschema": "^1.4.0", + "lit": "^3.2.0", + "minimist": "^1.2.5", + "parse5": "^7.1.1", + "source-map-support": "^0.5.19", + "typescript": "~5.5.0" + }, + "bin": { + "lit-localize": "bin/lit-localize.js" + } + }, + "node_modules/@lit/localize-tools/node_modules/typescript": { + "version": "5.5.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", + "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/@lit/reactive-element": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-2.0.4.tgz", + "integrity": "sha512-GFn91inaUa2oHLak8awSIigYz0cU0Payr1rcFsrkf5OJ5eSPxElyZfKh0f2p9FsTiZWXQdWGJeXZICEfXXYSXQ==", + "dependencies": { + "@lit-labs/ssr-dom-shim": "^1.2.0" + } + }, + "node_modules/@mdx-js/react": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-3.1.0.tgz", + "integrity": "sha512-QjHtSaoameoalGnKDT3FoIl4+9RwyTmo9ZJGBdLOks/YOiWHoRDI3PUwEzOE7kEmGcV3AFcp9K6dYu9rEuKLAQ==", + "dev": true, + "dependencies": { + "@types/mdx": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "@types/react": ">=16", + "react": ">=16" + } + }, + "node_modules/@microsoft/tsdoc": { + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.15.1.tgz", + "integrity": "sha512-4aErSrCR/On/e5G2hDP0wjooqDdauzEbIq8hIkIe5pXV0rtWJZvdCEKL0ykZxex+IxIwBp0eGeV48hQN07dXtw==", + "dev": true + }, + "node_modules/@microsoft/tsdoc-config": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/@microsoft/tsdoc-config/-/tsdoc-config-0.17.1.tgz", + "integrity": "sha512-UtjIFe0C6oYgTnad4q1QP4qXwLhe6tIpNTRStJ2RZEPIkqQPREAwE5spzVxsdn9UaEMUqhh0AqSx3X4nWAKXWw==", + "dev": true, + "dependencies": { + "@microsoft/tsdoc": "0.15.1", + "ajv": "~8.12.0", + "jju": "~1.4.0", + "resolve": "~1.22.2" + } + }, + "node_modules/@microsoft/tsdoc-config/node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@microsoft/tsdoc-config/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@open-wc/dedupe-mixin": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@open-wc/dedupe-mixin/-/dedupe-mixin-1.4.0.tgz", + "integrity": "sha512-Sj7gKl1TLcDbF7B6KUhtvr+1UCxdhMbNY5KxdU5IfMFWqL8oy1ZeAcCANjoB1TL0AJTcPmcCFsCbHf8X2jGDUA==", + "dev": true + }, + "node_modules/@open-wc/lit-helpers": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@open-wc/lit-helpers/-/lit-helpers-0.7.0.tgz", + "integrity": "sha512-4NBlx5ve0EvZplCRJbESm0MdMbRCw16alP2y76KAAAwzmFFXXrUj5hFwhw55+sSg5qaRRx6sY+s7usKgnNo3TQ==", + "dev": true, + "peerDependencies": { + "lit": "^2.0.0 || ^3.0.0" + } + }, + "node_modules/@open-wc/scoped-elements": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@open-wc/scoped-elements/-/scoped-elements-3.0.5.tgz", + "integrity": "sha512-q4U+hFTQQRyorJILOpmBm6PY2hgjCnQe214nXJNjbJMQ9EvT55oyZ7C8BY5aFYJkytUyBoawlMpZt4F2xjdzHw==", + "dev": true, + "dependencies": { + "@open-wc/dedupe-mixin": "^1.4.0", + "lit": "^3.0.0" + } + }, + "node_modules/@open-wc/semantic-dom-diff": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@open-wc/semantic-dom-diff/-/semantic-dom-diff-0.20.1.tgz", + "integrity": "sha512-mPF/RPT2TU7Dw41LEDdaeP6eyTOWBD4z0+AHP4/d0SbgcfJZVRymlIB6DQmtz0fd2CImIS9kszaMmwMt92HBPA==", + "dev": true, + "dependencies": { + "@types/chai": "^4.3.1", + "@web/test-runner-commands": "^0.9.0" + } + }, + "node_modules/@open-wc/testing": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@open-wc/testing/-/testing-4.0.0.tgz", + "integrity": "sha512-KI70O0CJEpBWs3jrTju4BFCy7V/d4tFfYWkg8pMzncsDhD7TYNHLw5cy+s1FHXIgVFetnMDhPpwlKIPvtTQW7w==", + "dev": true, + "dependencies": { + "@esm-bundle/chai": "^4.3.4-fix.0", + "@open-wc/semantic-dom-diff": "^0.20.0", + "@open-wc/testing-helpers": "^3.0.0", + "@types/chai-dom": "^1.11.0", + "@types/sinon-chai": "^3.2.3", + "chai-a11y-axe": "^1.5.0" + } + }, + "node_modules/@open-wc/testing-helpers": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@open-wc/testing-helpers/-/testing-helpers-3.0.1.tgz", + "integrity": "sha512-hyNysSatbgT2FNxHJsS3rGKcLEo6+HwDFu1UQL6jcSQUabp/tj3PyX7UnXL3H5YGv0lJArdYLSnvjLnjn3O2fw==", + "dev": true, + "dependencies": { + "@open-wc/scoped-elements": "^3.0.2", + "lit": "^2.0.0 || ^3.0.0", + "lit-html": "^2.0.0 || ^3.0.0" + } + }, + "node_modules/@parse5/tools": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@parse5/tools/-/tools-0.3.0.tgz", + "integrity": "sha512-zxRyTHkqb7WQMV8kTNBKWb1BeOFUKXBXTBWuxg9H9hfvQB3IwP6Iw2U75Ia5eyRxPNltmY7E8YAlz6zWwUnjKg==", + "dev": true, + "dependencies": { + "parse5": "^7.0.0" + } + }, + "node_modules/@puppeteer/browsers": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.4.1.tgz", + "integrity": "sha512-0kdAbmic3J09I6dT8e9vE2JOCSt13wHCW5x/ly8TSt2bDtuIWe2TgLZZDHdcziw9AVCzflMAXCrVyRIhIs44Ng==", + "dev": true, + "dependencies": { + "debug": "^4.3.7", + "extract-zip": "^2.0.1", + "progress": "^2.0.3", + "proxy-agent": "^6.4.0", + "semver": "^7.6.3", + "tar-fs": "^3.0.6", + "unbzip2-stream": "^1.4.3", + "yargs": "^17.7.2" + }, + "bin": { + "browsers": "lib/cjs/main-cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@puppeteer/browsers/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@remcovaes/web-test-runner-vite-plugin": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@remcovaes/web-test-runner-vite-plugin/-/web-test-runner-vite-plugin-1.2.2.tgz", + "integrity": "sha512-QMqk9HMnaK/GNTBt3UXqKoxlmnptFjaMBvrgXRj0RhZ59ckHkaPcUeN8lT8fo5WhpOqvNQ4l0jEx7zG4SioyLA==", + "dev": true, + "peerDependencies": { + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0" + } + }, + "node_modules/@rollup/plugin-node-resolve": { + "version": "15.3.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.3.0.tgz", + "integrity": "sha512-9eO5McEICxMzJpDW9OnMYSv4Sta3hmt7VtBFz5zR9273suNOydOyq/FrGeGy+KsTRFm8w0SLVhzig2ILFT63Ag==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "@types/resolve": "1.20.2", + "deepmerge": "^4.2.2", + "is-module": "^1.0.0", + "resolve": "^1.22.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.78.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.3.tgz", + "integrity": "sha512-Pnsb6f32CD2W3uCaLZIzDmeFyQ2b8UWMFI7xtwUezpcGBDVDW6y9XgAWIlARiGAo6eNF5FK5aQTr0LFyNyqq5A==", + "dev": true, + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.28.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.28.0.tgz", + "integrity": "sha512-wLJuPLT6grGZsy34g4N1yRfYeouklTgPhH1gWXCYspenKYD0s3cR99ZevOGw5BexMNywkbV3UkjADisozBmpPQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.28.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.28.0.tgz", + "integrity": "sha512-eiNkznlo0dLmVG/6wf+Ifi/v78G4d4QxRhuUl+s8EWZpDewgk7PX3ZyECUXU0Zq/Ca+8nU8cQpNC4Xgn2gFNDA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.28.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.28.0.tgz", + "integrity": "sha512-lmKx9yHsppblnLQZOGxdO66gT77bvdBtr/0P+TPOseowE7D9AJoBw8ZDULRasXRWf1Z86/gcOdpBrV6VDUY36Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.28.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.28.0.tgz", + "integrity": "sha512-8hxgfReVs7k9Js1uAIhS6zq3I+wKQETInnWQtgzt8JfGx51R1N6DRVy3F4o0lQwumbErRz52YqwjfvuwRxGv1w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.28.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.28.0.tgz", + "integrity": "sha512-lA1zZB3bFx5oxu9fYud4+g1mt+lYXCoch0M0V/xhqLoGatbzVse0wlSQ1UYOWKpuSu3gyN4qEc0Dxf/DII1bhQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.28.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.28.0.tgz", + "integrity": "sha512-aI2plavbUDjCQB/sRbeUZWX9qp12GfYkYSJOrdYTL/C5D53bsE2/nBPuoiJKoWp5SN78v2Vr8ZPnB+/VbQ2pFA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.28.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.28.0.tgz", + "integrity": "sha512-WXveUPKtfqtaNvpf0iOb0M6xC64GzUX/OowbqfiCSXTdi/jLlOmH0Ba94/OkiY2yTGTwteo4/dsHRfh5bDCZ+w==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.28.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.28.0.tgz", + "integrity": "sha512-yLc3O2NtOQR67lI79zsSc7lk31xjwcaocvdD1twL64PK1yNaIqCeWI9L5B4MFPAVGEVjH5k1oWSGuYX1Wutxpg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.28.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.28.0.tgz", + "integrity": "sha512-+P9G9hjEpHucHRXqesY+3X9hD2wh0iNnJXX/QhS/J5vTdG6VhNYMxJ2rJkQOxRUd17u5mbMLHM7yWGZdAASfcg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.28.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.28.0.tgz", + "integrity": "sha512-1xsm2rCKSTpKzi5/ypT5wfc+4bOGa/9yI/eaOLW0oMs7qpC542APWhl4A37AENGZ6St6GBMWhCCMM6tXgTIplw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.28.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.28.0.tgz", + "integrity": "sha512-zgWxMq8neVQeXL+ouSf6S7DoNeo6EPgi1eeqHXVKQxqPy1B2NvTbaOUWPn/7CfMKL7xvhV0/+fq/Z/J69g1WAQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.28.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.28.0.tgz", + "integrity": "sha512-VEdVYacLniRxbRJLNtzwGt5vwS0ycYshofI7cWAfj7Vg5asqj+pt+Q6x4n+AONSZW/kVm+5nklde0qs2EUwU2g==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.28.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.28.0.tgz", + "integrity": "sha512-LQlP5t2hcDJh8HV8RELD9/xlYtEzJkm/aWGsauvdO2ulfl3QYRjqrKW+mGAIWP5kdNCBheqqqYIGElSRCaXfpw==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.28.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.28.0.tgz", + "integrity": "sha512-Nl4KIzteVEKE9BdAvYoTkW19pa7LR/RBrT6F1dJCV/3pbjwDcaOq+edkP0LXuJ9kflW/xOK414X78r+K84+msw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.28.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.28.0.tgz", + "integrity": "sha512-eKpJr4vBDOi4goT75MvW+0dXcNUqisK4jvibY9vDdlgLx+yekxSm55StsHbxUsRxSTt3JEQvlr3cGDkzcSP8bw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.28.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.28.0.tgz", + "integrity": "sha512-Vi+WR62xWGsE/Oj+mD0FNAPY2MEox3cfyG0zLpotZdehPFXwz6lypkGs5y38Jd/NVSbOD02aVad6q6QYF7i8Bg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.28.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.28.0.tgz", + "integrity": "sha512-kN/Vpip8emMLn/eOza+4JwqDZBL6MPNpkdaEsgUtW1NYN3DZvZqSQrbKzJcTL6hd8YNmFTn7XGWMwccOcJBL0A==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.28.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.28.0.tgz", + "integrity": "sha512-Bvno2/aZT6usSa7lRDL2+hMjVAGjuqaymF1ApZm31JXzniR/hvr14jpU+/z4X6Gt5BPlzosscyJZGUvguXIqeQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@sideway/address": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", + "integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==", + "dev": true, + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@sideway/formula": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", + "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==", + "dev": true + }, + "node_modules/@sideway/pinpoint": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", + "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==", + "dev": true + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true + }, + "node_modules/@sindresorhus/merge-streams": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz", + "integrity": "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/@sinonjs/samsam": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.2.tgz", + "integrity": "sha512-v46t/fwnhejRSFTGqbpn9u+LQ9xJDse10gNnPgAcxgdoCDMXj/G2asWAC/8Qs+BAZDicX+MNZouXT1A7c83kVw==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^3.0.1", + "lodash.get": "^4.4.2", + "type-detect": "^4.1.0" + } + }, + "node_modules/@sinonjs/samsam/node_modules/type-detect": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", + "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@sinonjs/text-encoding": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.3.tgz", + "integrity": "sha512-DE427ROAphMQzU4ENbliGYrBSYPXF+TtLg9S8vzeA+OF4ZKzoDdzfL8sxuMUGS/lgRhM6j1URSk9ghf7Xo1tyA==", + "dev": true + }, + "node_modules/@storybook/addon-a11y": { + "version": "8.4.6", + "resolved": "https://registry.npmjs.org/@storybook/addon-a11y/-/addon-a11y-8.4.6.tgz", + "integrity": "sha512-Z6x/yfStplSROgmBTtiJ8LJgTqPgzW3Q7KXi+l+KoZ0pht6Nz9cYfcyygLCaftBk1ZaL7SDDIrjCP0H1NwfYiQ==", + "dev": true, + "dependencies": { + "@storybook/addon-highlight": "8.4.6", + "axe-core": "^4.2.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "storybook": "^8.4.6" + } + }, + "node_modules/@storybook/addon-actions": { + "version": "8.4.6", + "resolved": "https://registry.npmjs.org/@storybook/addon-actions/-/addon-actions-8.4.6.tgz", + "integrity": "sha512-vbplwjMj7UXbdzoFhQkqFHLQAPJX8OVGTM9Q+yjuWDHViaKKUlgRWp0jclT7aIDNJQU2a6wJbTimHgJeF16Vhg==", + "dev": true, + "dependencies": { + "@storybook/global": "^5.0.0", + "@types/uuid": "^9.0.1", + "dequal": "^2.0.2", + "polished": "^4.2.2", + "uuid": "^9.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "storybook": "^8.4.6" + } + }, + "node_modules/@storybook/addon-backgrounds": { + "version": "8.4.6", + "resolved": "https://registry.npmjs.org/@storybook/addon-backgrounds/-/addon-backgrounds-8.4.6.tgz", + "integrity": "sha512-RSjJ3iElxlQXebZrz1s5LeoLpAXr9LAGifX7w0abMzN5sg6QSwNeUHko2eT3V57M3k1Fa/5Eelso/QBQifFEog==", + "dev": true, + "dependencies": { + "@storybook/global": "^5.0.0", + "memoizerific": "^1.11.3", + "ts-dedent": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "storybook": "^8.4.6" + } + }, + "node_modules/@storybook/addon-controls": { + "version": "8.4.6", + "resolved": "https://registry.npmjs.org/@storybook/addon-controls/-/addon-controls-8.4.6.tgz", + "integrity": "sha512-70pEGWh0C2g8s0DYsISElOzsMbQS6p/K9iU5EqfotDF+hvEqstjsV/bTbR5f3OK4vR/7Gxamk7j8RVd14Nql6A==", + "dev": true, + "dependencies": { + "@storybook/global": "^5.0.0", + "dequal": "^2.0.2", + "ts-dedent": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "storybook": "^8.4.6" + } + }, + "node_modules/@storybook/addon-designs": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/@storybook/addon-designs/-/addon-designs-8.0.4.tgz", + "integrity": "sha512-BrEWks1BRnZis2e8OoE1LhFS+x2d094Tzpbb3jQBve2IfDv/X006RSuy1WyplNxskdYdBESCH45MlRn4lhP5ew==", + "dev": true, + "dependencies": { + "@figspec/react": "^1.0.0" + }, + "peerDependencies": { + "@storybook/blocks": "^8.0.0 || ^8.1.0-0 || ^8.2.0-0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0", + "@storybook/components": "^8.0.0 || ^8.1.0-0 || ^8.2.0-0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0", + "@storybook/theming": "^8.0.0 || ^8.1.0-0 || ^8.2.0-0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@storybook/blocks": { + "optional": true + }, + "@storybook/components": { + "optional": true + }, + "@storybook/theming": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/@storybook/addon-docs": { + "version": "8.4.6", + "resolved": "https://registry.npmjs.org/@storybook/addon-docs/-/addon-docs-8.4.6.tgz", + "integrity": "sha512-olxz61W7PW/EsXrKhLrYbI3rn9GMBhY3KIOF/6tumbRkh0Siu/qe4EAImaV9NNwiC1R7+De/1OIVMY6o0EIZVw==", + "dev": true, + "dependencies": { + "@mdx-js/react": "^3.0.0", + "@storybook/blocks": "8.4.6", + "@storybook/csf-plugin": "8.4.6", + "@storybook/react-dom-shim": "8.4.6", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0", + "ts-dedent": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "storybook": "^8.4.6" + } + }, + "node_modules/@storybook/addon-essentials": { + "version": "8.4.6", + "resolved": "https://registry.npmjs.org/@storybook/addon-essentials/-/addon-essentials-8.4.6.tgz", + "integrity": "sha512-TbFqyvWFUKw8LBpVcZuGQydzVB/3kSuHxDHi+Wj3Qas3cxBl7+w4/HjwomT2D2Tni1dZ1uPDOsAtNLmwp1POsg==", + "dev": true, + "dependencies": { + "@storybook/addon-actions": "8.4.6", + "@storybook/addon-backgrounds": "8.4.6", + "@storybook/addon-controls": "8.4.6", + "@storybook/addon-docs": "8.4.6", + "@storybook/addon-highlight": "8.4.6", + "@storybook/addon-measure": "8.4.6", + "@storybook/addon-outline": "8.4.6", + "@storybook/addon-toolbars": "8.4.6", + "@storybook/addon-viewport": "8.4.6", + "ts-dedent": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "storybook": "^8.4.6" + } + }, + "node_modules/@storybook/addon-highlight": { + "version": "8.4.6", + "resolved": "https://registry.npmjs.org/@storybook/addon-highlight/-/addon-highlight-8.4.6.tgz", + "integrity": "sha512-m8wedbqDMbwkP99dNHkHAiAUkx5E7FEEEyLPX1zfkhZWOGtTkavXHH235SGp50zD75LQ6eC/BvgegrzxSQa9Wg==", + "dev": true, + "dependencies": { + "@storybook/global": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "storybook": "^8.4.6" + } + }, + "node_modules/@storybook/addon-links": { + "version": "8.4.6", + "resolved": "https://registry.npmjs.org/@storybook/addon-links/-/addon-links-8.4.6.tgz", + "integrity": "sha512-1KoG9ytEWWwdF/dheu1O0dayQTMsHw++Qk8afqw7bwW1Cxz5LuAJH5ZscFWMiE5f4Xq1NgaJdeAUaIavyoOcdg==", + "dev": true, + "dependencies": { + "@storybook/csf": "^0.1.11", + "@storybook/global": "^5.0.0", + "ts-dedent": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", + "storybook": "^8.4.6" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + } + } + }, + "node_modules/@storybook/addon-measure": { + "version": "8.4.6", + "resolved": "https://registry.npmjs.org/@storybook/addon-measure/-/addon-measure-8.4.6.tgz", + "integrity": "sha512-N2IRpr39g5KpexCAS1vIHJT+phc9Yilwm3PULds2rQ66VMTbkxobXJDdt0NS05g5n9/eDniroNQwdCeLg4tkpw==", + "dev": true, + "dependencies": { + "@storybook/global": "^5.0.0", + "tiny-invariant": "^1.3.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "storybook": "^8.4.6" + } + }, + "node_modules/@storybook/addon-outline": { + "version": "8.4.6", + "resolved": "https://registry.npmjs.org/@storybook/addon-outline/-/addon-outline-8.4.6.tgz", + "integrity": "sha512-EhcWx8OpK85HxQulLWzpWUHEwQpDYuAiKzsFj9ivAbfeljkIWNTG04mierfaH1xX016uL9RtLJL/zwBS5ChnFg==", + "dev": true, + "dependencies": { + "@storybook/global": "^5.0.0", + "ts-dedent": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "storybook": "^8.4.6" + } + }, + "node_modules/@storybook/addon-toolbars": { + "version": "8.4.6", + "resolved": "https://registry.npmjs.org/@storybook/addon-toolbars/-/addon-toolbars-8.4.6.tgz", + "integrity": "sha512-+Xao/uGa8FnYsyUiREUkYXWNysm3Aba8tL/Bwd+HufHtdiKJGa9lrXaC7VLCqBUaEjwqM3aaPwqEWIROsthmPQ==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "storybook": "^8.4.6" + } + }, + "node_modules/@storybook/addon-viewport": { + "version": "8.4.6", + "resolved": "https://registry.npmjs.org/@storybook/addon-viewport/-/addon-viewport-8.4.6.tgz", + "integrity": "sha512-BuQll5YzOCpMS7p5Rsw9wcmi8hTnEKyg6+qAbkZNfiZ2JhXCa1GFUqX725fF1whpYVQULtkQxU8r+vahoRn7Yg==", + "dev": true, + "dependencies": { + "memoizerific": "^1.11.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "storybook": "^8.4.6" + } + }, + "node_modules/@storybook/blocks": { + "version": "8.4.6", + "resolved": "https://registry.npmjs.org/@storybook/blocks/-/blocks-8.4.6.tgz", + "integrity": "sha512-Gzbx8hM7ZQIHlQELcFIMbY1v+r1Po4mlinq0QVPtKS4lBcW4eZIsesbxOaL+uFNrxb583TLFzXo0DbRPzS46sg==", + "dev": true, + "dependencies": { + "@storybook/csf": "^0.1.11", + "@storybook/icons": "^1.2.12", + "ts-dedent": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", + "storybook": "^8.4.6" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/@storybook/builder-vite": { + "version": "8.4.6", + "resolved": "https://registry.npmjs.org/@storybook/builder-vite/-/builder-vite-8.4.6.tgz", + "integrity": "sha512-PyJsaEPyuRFFEplpNUi+nbuJd7d1DC2dAZjpsaHTXyqg5iPIbkIgsbCJLUDeIXnUDqM/utjmMpN0sQKJuhIc6w==", + "dev": true, + "dependencies": { + "@storybook/csf-plugin": "8.4.6", + "browser-assert": "^1.2.1", + "ts-dedent": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "storybook": "^8.4.6", + "vite": "^4.0.0 || ^5.0.0 || ^6.0.0" + } + }, + "node_modules/@storybook/components": { + "version": "8.4.6", + "resolved": "https://registry.npmjs.org/@storybook/components/-/components-8.4.6.tgz", + "integrity": "sha512-9tKSJJCyFT5RZMRGyozTBJkr9C9Yfk1nuOE9XbDEE1Z+3/IypKR9+iwc5mfNBStDNY+rxtYWNLKBb5GPR2yhzA==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "storybook": "^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0" + } + }, + "node_modules/@storybook/core": { + "version": "8.4.6", + "resolved": "https://registry.npmjs.org/@storybook/core/-/core-8.4.6.tgz", + "integrity": "sha512-WeojVtHy0/t50tzw/15S+DLzKsj8BN9yWdo3vJMvm+nflLFvfq1XvD9WGOWeaFp8E/o3AP+4HprXG0r42KEJtA==", + "dev": true, + "dependencies": { + "@storybook/csf": "^0.1.11", + "better-opn": "^3.0.2", + "browser-assert": "^1.2.1", + "esbuild": "^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0", + "esbuild-register": "^3.5.0", + "jsdoc-type-pratt-parser": "^4.0.0", + "process": "^0.11.10", + "recast": "^0.23.5", + "semver": "^7.6.2", + "util": "^0.12.5", + "ws": "^8.2.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "prettier": "^2 || ^3" + }, + "peerDependenciesMeta": { + "prettier": { + "optional": true + } + } + }, + "node_modules/@storybook/core/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@storybook/core/node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "dev": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/@storybook/csf": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/@storybook/csf/-/csf-0.1.12.tgz", + "integrity": "sha512-9/exVhabisyIVL0VxTCxo01Tdm8wefIXKXfltAPTSr8cbLn5JAxGQ6QV3mjdecLGEOucfoVhAKtJfVHxEK1iqw==", + "dev": true, + "dependencies": { + "type-fest": "^2.19.0" + } + }, + "node_modules/@storybook/csf-plugin": { + "version": "8.4.6", + "resolved": "https://registry.npmjs.org/@storybook/csf-plugin/-/csf-plugin-8.4.6.tgz", + "integrity": "sha512-JDIT0czC4yMgKGNf39KTZr3zm5MusAZdn6LBrTfvWb7CrTCR4iVHa4lp2yb7EJk41vHsBec0QUYDDuiFH/vV0g==", + "dev": true, + "dependencies": { + "unplugin": "^1.3.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "storybook": "^8.4.6" + } + }, + "node_modules/@storybook/global": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@storybook/global/-/global-5.0.0.tgz", + "integrity": "sha512-FcOqPAXACP0I3oJ/ws6/rrPT9WGhu915Cg8D02a9YxLo0DE9zI+a9A5gRGvmQ09fiWPukqI8ZAEoQEdWUKMQdQ==", + "dev": true + }, + "node_modules/@storybook/icons": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@storybook/icons/-/icons-1.2.12.tgz", + "integrity": "sha512-UxgyK5W3/UV4VrI3dl6ajGfHM4aOqMAkFLWe2KibeQudLf6NJpDrDMSHwZj+3iKC4jFU7dkKbbtH2h/al4sW3Q==", + "dev": true, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/@storybook/instrumenter": { + "version": "8.4.6", + "resolved": "https://registry.npmjs.org/@storybook/instrumenter/-/instrumenter-8.4.6.tgz", + "integrity": "sha512-snXjlgbp065A6KoK9zkjBYEIMCSlN5JefPKzt1FC0rbcbtahhD+iPpqISKhDSczwgOku/JVhVUDp/vU7AIf4mg==", + "dev": true, + "dependencies": { + "@storybook/global": "^5.0.0", + "@vitest/utils": "^2.1.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "storybook": "^8.4.6" + } + }, + "node_modules/@storybook/manager-api": { + "version": "8.4.6", + "resolved": "https://registry.npmjs.org/@storybook/manager-api/-/manager-api-8.4.6.tgz", + "integrity": "sha512-TsXlQ5m5rTl2KNT9icPFyy822AqXrx1QplZBt/L7cFn7SpqQKDeSta21FH7MG0piAvzOweXebVSqKngJ6cCWWQ==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "storybook": "^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0" + } + }, + "node_modules/@storybook/preview-api": { + "version": "8.4.6", + "resolved": "https://registry.npmjs.org/@storybook/preview-api/-/preview-api-8.4.6.tgz", + "integrity": "sha512-LbD+lR1FGvWaJBXteVx5xdgs1x1D7tyidBg2CsW2ex+cP0iJ176JgjPfutZxlWOfQnhfRYNnJ3WKoCIfxFOTKA==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "storybook": "^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0" + } + }, + "node_modules/@storybook/react-dom-shim": { + "version": "8.4.6", + "resolved": "https://registry.npmjs.org/@storybook/react-dom-shim/-/react-dom-shim-8.4.6.tgz", + "integrity": "sha512-f7RM8GO++fqMxbjNdEzeGS1P821jXuwRnAraejk5hyjB5SqetauFxMwoFYEYfJXPaLX2qIubnIJ78hdJ/IBaEA==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", + "storybook": "^8.4.6" + } + }, + "node_modules/@storybook/test": { + "version": "8.4.6", + "resolved": "https://registry.npmjs.org/@storybook/test/-/test-8.4.6.tgz", + "integrity": "sha512-MeU1g65YgU66M2NtmEIL9gVeHk+en0k9Hp0wfxEO7NT/WLfaOD5RXLRDJVhbAlrH/6tLeWKIPNh/D26y27vO/g==", + "dev": true, + "dependencies": { + "@storybook/csf": "^0.1.11", + "@storybook/global": "^5.0.0", + "@storybook/instrumenter": "8.4.6", + "@testing-library/dom": "10.4.0", + "@testing-library/jest-dom": "6.5.0", + "@testing-library/user-event": "14.5.2", + "@vitest/expect": "2.0.5", + "@vitest/spy": "2.0.5" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "storybook": "^8.4.6" + } + }, + "node_modules/@storybook/test-runner": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@storybook/test-runner/-/test-runner-0.20.1.tgz", + "integrity": "sha512-3WU/th/uncIR6vpQDK9hKjiZjmczsluoLbgkRV7ufxY9IgHCGcbIjvT5EPS+XZIaOrNGjaPsyB5cE1okKn9iSA==", + "dev": true, + "dependencies": { + "@babel/core": "^7.22.5", + "@babel/generator": "^7.22.5", + "@babel/template": "^7.22.5", + "@babel/types": "^7.22.5", + "@jest/types": "^29.6.3", + "@storybook/csf": "^0.1.11", + "@swc/core": "^1.5.22", + "@swc/jest": "^0.2.23", + "expect-playwright": "^0.8.0", + "jest": "^29.6.4", + "jest-circus": "^29.6.4", + "jest-environment-node": "^29.6.4", + "jest-junit": "^16.0.0", + "jest-playwright-preset": "^4.0.0", + "jest-runner": "^29.6.4", + "jest-serializer-html": "^7.1.0", + "jest-watch-typeahead": "^2.0.0", + "nyc": "^15.1.0", + "playwright": "^1.14.0" + }, + "bin": { + "test-storybook": "dist/test-storybook.js" + }, + "engines": { + "node": "^16.10.0 || ^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "storybook": "^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0" + } + }, + "node_modules/@storybook/theming": { + "version": "8.4.6", + "resolved": "https://registry.npmjs.org/@storybook/theming/-/theming-8.4.6.tgz", + "integrity": "sha512-q7vDPN/mgj7cXIVQ9R1/V75hrzNgKkm2G0LjMo57//9/djQ+7LxvBsR1iScbFIRSEqppvMiBFzkts+2uXidySA==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "storybook": "^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0" + } + }, + "node_modules/@storybook/web-components": { + "version": "8.4.6", + "resolved": "https://registry.npmjs.org/@storybook/web-components/-/web-components-8.4.6.tgz", + "integrity": "sha512-Mblv35pwX+Pyo9D5qQjUE3WzHxACe+DgGdzU03QG5C8wEBuk4ji/s9TMoTwKVLBvXp13CuDsA1qPiH86sizdjA==", + "dev": true, + "dependencies": { + "@storybook/components": "8.4.6", + "@storybook/global": "^5.0.0", + "@storybook/manager-api": "8.4.6", + "@storybook/preview-api": "8.4.6", + "@storybook/theming": "8.4.6", + "tiny-invariant": "^1.3.1", + "ts-dedent": "^2.0.0" + }, + "engines": { + "node": ">=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "lit": "^2.0.0 || ^3.0.0", + "storybook": "^8.4.6" + } + }, + "node_modules/@storybook/web-components-vite": { + "version": "8.4.6", + "resolved": "https://registry.npmjs.org/@storybook/web-components-vite/-/web-components-vite-8.4.6.tgz", + "integrity": "sha512-OUaraqrR/hasT/laIvUndcwj1d9NLPcjULH8eh2FI8UQ80sZjz3htsFuOd3W6dxLqLVBIMMYne9mOoynYG9mCA==", + "dev": true, + "dependencies": { + "@storybook/builder-vite": "8.4.6", + "@storybook/web-components": "8.4.6", + "magic-string": "^0.30.0" + }, + "engines": { + "node": ">=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "storybook": "^8.4.6" + } + }, + "node_modules/@swc/core": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.9.3.tgz", + "integrity": "sha512-oRj0AFePUhtatX+BscVhnzaAmWjpfAeySpM1TCbxA1rtBDeH/JDhi5yYzAKneDYtVtBvA7ApfeuzhMC9ye4xSg==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "@swc/counter": "^0.1.3", + "@swc/types": "^0.1.17" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/swc" + }, + "optionalDependencies": { + "@swc/core-darwin-arm64": "1.9.3", + "@swc/core-darwin-x64": "1.9.3", + "@swc/core-linux-arm-gnueabihf": "1.9.3", + "@swc/core-linux-arm64-gnu": "1.9.3", + "@swc/core-linux-arm64-musl": "1.9.3", + "@swc/core-linux-x64-gnu": "1.9.3", + "@swc/core-linux-x64-musl": "1.9.3", + "@swc/core-win32-arm64-msvc": "1.9.3", + "@swc/core-win32-ia32-msvc": "1.9.3", + "@swc/core-win32-x64-msvc": "1.9.3" + }, + "peerDependencies": { + "@swc/helpers": "*" + }, + "peerDependenciesMeta": { + "@swc/helpers": { + "optional": true + } + } + }, + "node_modules/@swc/core-darwin-arm64": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.9.3.tgz", + "integrity": "sha512-hGfl/KTic/QY4tB9DkTbNuxy5cV4IeejpPD4zo+Lzt4iLlDWIeANL4Fkg67FiVceNJboqg48CUX+APhDHO5G1w==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-darwin-x64": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.9.3.tgz", + "integrity": "sha512-IaRq05ZLdtgF5h9CzlcgaNHyg4VXuiStnOFpfNEMuI5fm5afP2S0FHq8WdakUz5WppsbddTdplL+vpeApt/WCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm-gnueabihf": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.9.3.tgz", + "integrity": "sha512-Pbwe7xYprj/nEnZrNBvZfjnTxlBIcfApAGdz2EROhjpPj+FBqBa3wOogqbsuGGBdCphf8S+KPprL1z+oDWkmSQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-gnu": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.9.3.tgz", + "integrity": "sha512-AQ5JZiwNGVV/2K2TVulg0mw/3LYfqpjZO6jDPtR2evNbk9Yt57YsVzS+3vHSlUBQDRV9/jqMuZYVU3P13xrk+g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-musl": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.9.3.tgz", + "integrity": "sha512-tzVH480RY6RbMl/QRgh5HK3zn1ZTFsThuxDGo6Iuk1MdwIbdFYUY034heWUTI4u3Db97ArKh0hNL0xhO3+PZdg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-gnu": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.9.3.tgz", + "integrity": "sha512-ivXXBRDXDc9k4cdv10R21ccBmGebVOwKXT/UdH1PhxUn9m/h8erAWjz5pcELwjiMf27WokqPgaWVfaclDbgE+w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-musl": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.9.3.tgz", + "integrity": "sha512-ILsGMgfnOz1HwdDz+ZgEuomIwkP1PHT6maigZxaCIuC6OPEhKE8uYna22uU63XvYcLQvZYDzpR3ms47WQPuNEg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-arm64-msvc": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.9.3.tgz", + "integrity": "sha512-e+XmltDVIHieUnNJHtspn6B+PCcFOMYXNJB1GqoCcyinkEIQNwC8KtWgMqUucUbEWJkPc35NHy9k8aCXRmw9Kg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-ia32-msvc": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.9.3.tgz", + "integrity": "sha512-rqpzNfpAooSL4UfQnHhkW8aL+oyjqJniDP0qwZfGnjDoJSbtPysHg2LpcOBEdSnEH+uIZq6J96qf0ZFD8AGfXA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-x64-msvc": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.9.3.tgz", + "integrity": "sha512-3YJJLQ5suIEHEKc1GHtqVq475guiyqisKSoUnoaRtxkDaW5g1yvPt9IoSLOe2mRs7+FFhGGU693RsBUSwOXSdQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/counter": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", + "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", + "dev": true + }, + "node_modules/@swc/jest": { + "version": "0.2.37", + "resolved": "https://registry.npmjs.org/@swc/jest/-/jest-0.2.37.tgz", + "integrity": "sha512-CR2BHhmXKGxTiFr21DYPRHQunLkX3mNIFGFkxBGji6r9uyIR5zftTOVYj1e0sFNMV2H7mf/+vpaglqaryBtqfQ==", + "dev": true, + "dependencies": { + "@jest/create-cache-key-function": "^29.7.0", + "@swc/counter": "^0.1.3", + "jsonc-parser": "^3.2.0" + }, + "engines": { + "npm": ">= 7.0.0" + }, + "peerDependencies": { + "@swc/core": "*" + } + }, + "node_modules/@swc/types": { + "version": "0.1.17", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.17.tgz", + "integrity": "sha512-V5gRru+aD8YVyCOMAjMpWR1Ui577DD5KSJsHP8RAxopAH22jFz6GZd/qxqjO6MJHQhcsjvjOFXyDhyLQUnMveQ==", + "dev": true, + "dependencies": { + "@swc/counter": "^0.1.3" + } + }, + "node_modules/@testing-library/dom": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz", + "integrity": "sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.3.0", + "chalk": "^4.1.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@testing-library/jest-dom": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.5.0.tgz", + "integrity": "sha512-xGGHpBXYSHUUr6XsKBfs85TWlYKpTc37cSBBVrXcib2MkHLboWlkClhWF37JKlDb9KEq3dHs+f2xR7XJEWGBxA==", + "dev": true, + "dependencies": { + "@adobe/css-tools": "^4.4.0", + "aria-query": "^5.0.0", + "chalk": "^3.0.0", + "css.escape": "^1.5.1", + "dom-accessibility-api": "^0.6.3", + "lodash": "^4.17.21", + "redent": "^3.0.0" + }, + "engines": { + "node": ">=14", + "npm": ">=6", + "yarn": ">=1" + } + }, + "node_modules/@testing-library/jest-dom/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", + "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", + "dev": true + }, + "node_modules/@testing-library/user-event": { + "version": "14.5.2", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.5.2.tgz", + "integrity": "sha512-YAh82Wh4TIrxYLmfGcixwD18oIjyC1pFQC2Y01F2lzV2HTMiYrI0nze0FD0ocB//CKS/7jIUgae+adPqxK5yCQ==", + "dev": true, + "engines": { + "node": ">=12", + "npm": ">=6" + }, + "peerDependencies": { + "@testing-library/dom": ">=7.21.4" + } + }, + "node_modules/@thepassle/axobject-query": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@thepassle/axobject-query/-/axobject-query-4.0.0.tgz", + "integrity": "sha512-/LHo+2jOdxs2WtbGocr3/lDSzsnjgCV6DSoBf4Y1Q0D24Hu67NPWuneoJimfHu5auqqSWi1fAvtln2013VxVqg==", + "dev": true, + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/@tootallnate/quickjs-emscripten": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", + "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", + "dev": true + }, + "node_modules/@ts-morph/common": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.25.0.tgz", + "integrity": "sha512-kMnZz+vGGHi4GoHnLmMhGNjm44kGtKUXGnOvrKmMwAuvNjM/PgKVGfUnL7IDvK7Jb2QQ82jq3Zmp04Gy+r3Dkg==", + "dev": true, + "dependencies": { + "minimatch": "^9.0.4", + "path-browserify": "^1.0.1", + "tinyglobby": "^0.2.9" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", + "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", + "dev": true, + "optional": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true, + "optional": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true, + "optional": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true, + "optional": true + }, + "node_modules/@types/accepts": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/@types/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Pay9fq2lM2wXPWbteBsRAGiWH2hig4ZE2asK+mm7kUzlxRTfL961rj89I6zV/E3PcIkDqyuBEcMxFT7rccugeQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/aria-query": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", + "dev": true + }, + "node_modules/@types/babel__code-frame": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/@types/babel__code-frame/-/babel__code-frame-7.0.6.tgz", + "integrity": "sha512-Anitqkl3+KrzcW2k77lRlg/GfLZLWXBuNgbEcIOU6M92yw42vsd3xV/Z/yAHEj8m+KUjL6bWOVOFqX8PFPJ4LA==", + "dev": true + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.6.8", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", + "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.6", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.6.tgz", + "integrity": "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.5", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", + "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", + "dev": true, + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/chai": { + "version": "4.3.20", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.20.tgz", + "integrity": "sha512-/pC9HAB5I/xMlc5FP77qjCnI16ChlJfW0tGa0IUcFn38VJrTV6DeZ60NU5KZBtaOZqjdpwTWohz5HU1RrhiYxQ==", + "dev": true + }, + "node_modules/@types/chai-dom": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/@types/chai-dom/-/chai-dom-1.11.3.tgz", + "integrity": "sha512-EUEZI7uID4ewzxnU7DJXtyvykhQuwe+etJ1wwOiJyQRTH/ifMWKX+ghiXkxCUvNJ6IQDodf0JXhuP6zZcy2qXQ==", + "dev": true, + "dependencies": { + "@types/chai": "*" + } + }, + "node_modules/@types/co-body": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/@types/co-body/-/co-body-6.1.3.tgz", + "integrity": "sha512-UhuhrQ5hclX6UJctv5m4Rfp52AfG9o9+d9/HwjxhVB5NjXxr5t9oKgJxN8xRHgr35oo8meUEHUPFWiKg6y71aA==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/qs": "*" + } + }, + "node_modules/@types/command-line-args": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/command-line-args/-/command-line-args-5.2.3.tgz", + "integrity": "sha512-uv0aG6R0Y8WHZLTamZwtfsDLVRnOa+n+n5rEvFWL5Na5gZ8V2Teab/duDPFzIIIhs9qizDpcavCusCLJZu62Kw==", + "dev": true + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/content-disposition": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/@types/content-disposition/-/content-disposition-0.5.8.tgz", + "integrity": "sha512-QVSSvno3dE0MgO76pJhmv4Qyi/j0Yk9pBp0Y7TJ2Tlj+KCgJWY6qX7nnxCOLkZ3VYRSIk1WTxCvwUSdx6CCLdg==", + "dev": true + }, + "node_modules/@types/convert-source-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/convert-source-map/-/convert-source-map-2.0.3.tgz", + "integrity": "sha512-ag0BfJLZf6CQz8VIuRIEYQ5Ggwk/82uvTQf27RcpyDNbY0Vw49LIPqAxk5tqYfrCs9xDaIMvl4aj7ZopnYL8bA==", + "dev": true + }, + "node_modules/@types/cookies": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@types/cookies/-/cookies-0.9.0.tgz", + "integrity": "sha512-40Zk8qR147RABiQ7NQnBzWzDcjKzNrntB5BAmeGCb2p/MIyOE+4BVvc17wumsUqUw00bJYqoXFHYygQnEFh4/Q==", + "dev": true, + "dependencies": { + "@types/connect": "*", + "@types/express": "*", + "@types/keygrip": "*", + "@types/node": "*" + } + }, + "node_modules/@types/debounce": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@types/debounce/-/debounce-1.2.4.tgz", + "integrity": "sha512-jBqiORIzKDOToaF63Fm//haOCHuwQuLa2202RK4MozpA6lh93eCBc+/8+wZn5OzjJt3ySdc+74SXWXB55Ewtyw==", + "dev": true + }, + "node_modules/@types/estree": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "dev": true + }, + "node_modules/@types/express": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.0.tgz", + "integrity": "sha512-DvZriSMehGHL1ZNLzi6MidnsDhUZM/x2pRdDIKdwbUNqqwHxMlRdkxtn6/EPKyqKpHqTl/4nRZsRNLpZxZRpPQ==", + "dev": true, + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^5.0.0", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.2.tgz", + "integrity": "sha512-vluaspfvWEtE4vcSDlKRNer52DvOGrB2xv6diXy6UKyKW0lqZiWHGNApSyxOv+8DE5Z27IzVvE7hNkxg7EXIcg==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", + "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/http-assert": { + "version": "1.5.6", + "resolved": "https://registry.npmjs.org/@types/http-assert/-/http-assert-1.5.6.tgz", + "integrity": "sha512-TTEwmtjgVbYAzZYWyeHPrrtWnfVkm8tQkP8P21uQifPgMRgjrow3XDEYqucuC8SKZJT7pUnhU/JymvjggxO9vw==", + "dev": true + }, + "node_modules/@types/http-errors": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", + "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", + "dev": true + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jest": { + "version": "29.5.14", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.14.tgz", + "integrity": "sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==", + "dev": true, + "dependencies": { + "expect": "^29.0.0", + "pretty-format": "^29.0.0" + } + }, + "node_modules/@types/jest/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@types/jest/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@types/jest/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, + "node_modules/@types/junit-report-builder": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/junit-report-builder/-/junit-report-builder-3.0.2.tgz", + "integrity": "sha512-R5M+SYhMbwBeQcNXYWNCZkl09vkVfAtcPIaCGdzIkkbeaTrVbGQ7HVgi4s+EmM/M1K4ZuWQH0jGcvMvNePfxYA==", + "dev": true + }, + "node_modules/@types/keygrip": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/keygrip/-/keygrip-1.0.6.tgz", + "integrity": "sha512-lZuNAY9xeJt7Bx4t4dx0rYCDqGPW8RXhQZK1td7d4H6E9zYbLoOtjBvfwdTKpsyxQI/2jv+armjX/RW+ZNpXOQ==", + "dev": true + }, + "node_modules/@types/koa": { + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/@types/koa/-/koa-2.15.0.tgz", + "integrity": "sha512-7QFsywoE5URbuVnG3loe03QXuGajrnotr3gQkXcEBShORai23MePfFYdhz90FEtBBpkyIYQbVD+evKtloCgX3g==", + "dev": true, + "dependencies": { + "@types/accepts": "*", + "@types/content-disposition": "*", + "@types/cookies": "*", + "@types/http-assert": "*", + "@types/http-errors": "*", + "@types/keygrip": "*", + "@types/koa-compose": "*", + "@types/node": "*" + } + }, + "node_modules/@types/koa-compose": { + "version": "3.2.8", + "resolved": "https://registry.npmjs.org/@types/koa-compose/-/koa-compose-3.2.8.tgz", + "integrity": "sha512-4Olc63RY+MKvxMwVknCUDhRQX1pFQoBZ/lXcRLP69PQkEpze/0cr8LNqJQe5NFb/b19DWi2a5bTi2VAlQzhJuA==", + "dev": true, + "dependencies": { + "@types/koa": "*" + } + }, + "node_modules/@types/mdx": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.13.tgz", + "integrity": "sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw==", + "dev": true + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true + }, + "node_modules/@types/node": { + "version": "22.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.1.tgz", + "integrity": "sha512-qKgsUwfHZV2WCWLAnVP1JqnpE6Im6h3Y0+fYgMTasNQ7V++CBX5OT1as0g0f+OyubbFqhf6XVNIsmN4IIhEgGQ==", + "dev": true, + "dependencies": { + "undici-types": "~6.20.0" + } + }, + "node_modules/@types/parse5": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@types/parse5/-/parse5-6.0.3.tgz", + "integrity": "sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g==", + "dev": true + }, + "node_modules/@types/prop-types": { + "version": "15.7.13", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz", + "integrity": "sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==", + "dev": true, + "peer": true + }, + "node_modules/@types/qs": { + "version": "6.9.17", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.17.tgz", + "integrity": "sha512-rX4/bPcfmvxHDv0XjfJELTTr+iB+tn032nPILqHm5wbthUUUuVtNGGqzhya9XUxjTP8Fpr0qYgSZZKxGY++svQ==", + "dev": true + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true + }, + "node_modules/@types/react": { + "version": "18.3.12", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.12.tgz", + "integrity": "sha512-D2wOSq/d6Agt28q7rSI3jhU7G6aiuzljDGZ2hTZHIkrTLUI+AF3WMeKkEZ9nN2fkBAlcktT6vcZjDFiIhMYEQw==", + "dev": true, + "peer": true, + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/resolve": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", + "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", + "dev": true + }, + "node_modules/@types/send": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", + "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "dev": true, + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.7", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", + "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", + "dev": true, + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" + } + }, + "node_modules/@types/sinon": { + "version": "17.0.3", + "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-17.0.3.tgz", + "integrity": "sha512-j3uovdn8ewky9kRBG19bOwaZbexJu/XjtkHyjvUgt4xfPFz18dcORIMqnYh66Fx3Powhcr85NT5+er3+oViapw==", + "dev": true, + "dependencies": { + "@types/sinonjs__fake-timers": "*" + } + }, + "node_modules/@types/sinon-chai": { + "version": "3.2.12", + "resolved": "https://registry.npmjs.org/@types/sinon-chai/-/sinon-chai-3.2.12.tgz", + "integrity": "sha512-9y0Gflk3b0+NhQZ/oxGtaAJDvRywCa5sIyaVnounqLvmf93yBF4EgIRspePtkMs3Tr844nCclYMlcCNmLCvjuQ==", + "dev": true, + "dependencies": { + "@types/chai": "*", + "@types/sinon": "*" + } + }, + "node_modules/@types/sinonjs__fake-timers": { + "version": "8.1.5", + "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.5.tgz", + "integrity": "sha512-mQkU2jY8jJEF7YHjHvsQO8+3ughTL1mcnn96igfhONmR+fUPSKIkefQYpSe8bsly2Ep7oQbn/6VG5/9/0qcArQ==", + "dev": true + }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true + }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==" + }, + "node_modules/@types/uuid": { + "version": "9.0.8", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.8.tgz", + "integrity": "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==", + "dev": true + }, + "node_modules/@types/wait-on": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/@types/wait-on/-/wait-on-5.3.4.tgz", + "integrity": "sha512-EBsPjFMrFlMbbUFf9D1Fp+PAB2TwmUn7a3YtHyD9RLuTIk1jDd8SxXVAoez2Ciy+8Jsceo2MYEYZzJ/DvorOKw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/ws": { + "version": "7.4.7", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.4.7.tgz", + "integrity": "sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/yargs": { + "version": "17.0.33", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", + "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true + }, + "node_modules/@types/yauzl": { + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", + "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", + "dev": true, + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.17.0.tgz", + "integrity": "sha512-HU1KAdW3Tt8zQkdvNoIijfWDMvdSweFYm4hWh+KwhPstv+sCmWb89hCIP8msFm9N1R/ooh9honpSuvqKWlYy3w==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.17.0", + "@typescript-eslint/type-utils": "8.17.0", + "@typescript-eslint/utils": "8.17.0", + "@typescript-eslint/visitor-keys": "8.17.0", + "graphemer": "^1.4.0", + "ignore": "^5.3.1", + "natural-compare": "^1.4.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", + "eslint": "^8.57.0 || ^9.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.17.0.tgz", + "integrity": "sha512-Drp39TXuUlD49F7ilHHCG7TTg8IkA+hxCuULdmzWYICxGXvDXmDmWEjJYZQYgf6l/TFfYNE167m7isnc3xlIEg==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "8.17.0", + "@typescript-eslint/types": "8.17.0", + "@typescript-eslint/typescript-estree": "8.17.0", + "@typescript-eslint/visitor-keys": "8.17.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.17.0.tgz", + "integrity": "sha512-/ewp4XjvnxaREtqsZjF4Mfn078RD/9GmiEAtTeLQ7yFdKnqwTOgRMSvFz4et9U5RiJQ15WTGXPLj89zGusvxBg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.17.0", + "@typescript-eslint/visitor-keys": "8.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.17.0.tgz", + "integrity": "sha512-q38llWJYPd63rRnJ6wY/ZQqIzPrBCkPdpIsaCfkR3Q4t3p6sb422zougfad4TFW9+ElIFLVDzWGiGAfbb/v2qw==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "8.17.0", + "@typescript-eslint/utils": "8.17.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.17.0.tgz", + "integrity": "sha512-gY2TVzeve3z6crqh2Ic7Cr+CAv6pfb0Egee7J5UAVWCpVvDI/F71wNfolIim4FE6hT15EbpZFVUj9j5i38jYXA==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.17.0.tgz", + "integrity": "sha512-JqkOopc1nRKZpX+opvKqnM3XUlM7LpFMD0lYxTqOTKQfCWAmxw45e3qlOCsEqEB2yuacujivudOFpCnqkBDNMw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.17.0", + "@typescript-eslint/visitor-keys": "8.17.0", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.17.0.tgz", + "integrity": "sha512-bQC8BnEkxqG8HBGKwG9wXlZqg37RKSMY7v/X8VEWD8JG2JuTHuNK0VFvMPMUKQcbk6B+tf05k+4AShAEtCtJ/w==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "8.17.0", + "@typescript-eslint/types": "8.17.0", + "@typescript-eslint/typescript-estree": "8.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.17.0.tgz", + "integrity": "sha512-1Hm7THLpO6ww5QU6H/Qp+AusUUl+z/CAm3cNZZ0jQvon9yicgO7Rwd+/WWRpMKLYV6p2UvdbR27c86rzCPpreg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.17.0", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@vitest/expect": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.0.5.tgz", + "integrity": "sha512-yHZtwuP7JZivj65Gxoi8upUN2OzHTi3zVfjwdpu2WrvCZPLwsJ2Ey5ILIPccoW23dd/zQBlJ4/dhi7DWNyXCpA==", + "dev": true, + "dependencies": { + "@vitest/spy": "2.0.5", + "@vitest/utils": "2.0.5", + "chai": "^5.1.1", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/expect/node_modules/@vitest/pretty-format": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.0.5.tgz", + "integrity": "sha512-h8k+1oWHfwTkyTkb9egzwNMfJAEx4veaPSnMeKbVSjp4euqGSbQlm5+6VHwTr7u4FJslVVsUG5nopCaAYdOmSQ==", + "dev": true, + "dependencies": { + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/expect/node_modules/@vitest/utils": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.0.5.tgz", + "integrity": "sha512-d8HKbqIcya+GR67mkZbrzhS5kKhtp8dQLcmRZLGTscGVg7yImT82cIrhtn2L8+VujWcy6KZweApgNmPsTAO/UQ==", + "dev": true, + "dependencies": { + "@vitest/pretty-format": "2.0.5", + "estree-walker": "^3.0.3", + "loupe": "^3.1.1", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/expect/node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/@vitest/pretty-format": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.8.tgz", + "integrity": "sha512-9HiSZ9zpqNLKlbIDRWOnAWqgcA7xu+8YxXSekhr0Ykab7PAYFkhkwoqVArPOtJhPmYeE2YHgKZlj3CP36z2AJQ==", + "dev": true, + "dependencies": { + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.0.5.tgz", + "integrity": "sha512-c/jdthAhvJdpfVuaexSrnawxZz6pywlTPe84LUB2m/4t3rl2fTo9NFGBG4oWgaD+FTgDDV8hJ/nibT7IfH3JfA==", + "dev": true, + "dependencies": { + "tinyspy": "^3.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.8.tgz", + "integrity": "sha512-dwSoui6djdwbfFmIgbIjX2ZhIoG7Ex/+xpxyiEgIGzjliY8xGkcpITKTlp6B4MgtGkF2ilvm97cPM96XZaAgcA==", + "dev": true, + "dependencies": { + "@vitest/pretty-format": "2.1.8", + "loupe": "^3.1.2", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vscode/web-custom-data": { + "version": "0.4.13", + "resolved": "https://registry.npmjs.org/@vscode/web-custom-data/-/web-custom-data-0.4.13.tgz", + "integrity": "sha512-2ZUIRfhofZ/npLlf872EBnPmn27Kt4M2UssmQIfnJvgGgMYZJ5fvtHEDnttBBf2hnVtBgNCqZMVHJA+wsFVqTA==", + "dev": true + }, + "node_modules/@web/browser-logs": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@web/browser-logs/-/browser-logs-0.4.0.tgz", + "integrity": "sha512-/EBiDAUCJ2DzZhaFxTPRIznEPeafdLbXShIL6aTu7x73x7ZoxSDv7DGuTsh2rWNMUa4+AKli4UORrpyv6QBOiA==", + "dev": true, + "dependencies": { + "errorstacks": "^2.2.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@web/config-loader": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@web/config-loader/-/config-loader-0.1.3.tgz", + "integrity": "sha512-XVKH79pk4d3EHRhofete8eAnqto1e8mCRAqPV00KLNFzCWSe8sWmLnqKCqkPNARC6nksMaGrATnA5sPDRllMpQ==", + "dev": true, + "dependencies": { + "semver": "^7.3.4" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@web/config-loader/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@web/dev-server": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/@web/dev-server/-/dev-server-0.4.6.tgz", + "integrity": "sha512-jj/1bcElAy5EZet8m2CcUdzxT+CRvUjIXGh8Lt7vxtthkN9PzY9wlhWx/9WOs5iwlnG1oj0VGo6f/zvbPO0s9w==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.12.11", + "@types/command-line-args": "^5.0.0", + "@web/config-loader": "^0.3.0", + "@web/dev-server-core": "^0.7.2", + "@web/dev-server-rollup": "^0.6.1", + "camelcase": "^6.2.0", + "command-line-args": "^5.1.1", + "command-line-usage": "^7.0.1", + "debounce": "^1.2.0", + "deepmerge": "^4.2.2", + "internal-ip": "^6.2.0", + "nanocolors": "^0.2.1", + "open": "^8.0.2", + "portfinder": "^1.0.32" + }, + "bin": { + "wds": "dist/bin.js", + "web-dev-server": "dist/bin.js" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@web/dev-server-core": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/@web/dev-server-core/-/dev-server-core-0.7.4.tgz", + "integrity": "sha512-nHSNrJ1J9GjmSceKNHpWRMjvpfE2NTV9EYUffPIr7j0sIV59gK7NI/4+9slotJ/ODXw0+e1gSeJshTOhjjVNxQ==", + "dev": true, + "dependencies": { + "@types/koa": "^2.11.6", + "@types/ws": "^7.4.0", + "@web/parse5-utils": "^2.1.0", + "chokidar": "^4.0.1", + "clone": "^2.1.2", + "es-module-lexer": "^1.0.0", + "get-stream": "^6.0.0", + "is-stream": "^2.0.0", + "isbinaryfile": "^5.0.0", + "koa": "^2.13.0", + "koa-etag": "^4.0.0", + "koa-send": "^5.0.1", + "koa-static": "^5.0.0", + "lru-cache": "^8.0.4", + "mime-types": "^2.1.27", + "parse5": "^6.0.1", + "picomatch": "^2.2.2", + "ws": "^7.5.10" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@web/dev-server-core/node_modules/chokidar": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.1.tgz", + "integrity": "sha512-n8enUVCED/KVRQlab1hr3MVpcVMvxtZjmEa956u+4YijlmQED223XMSYj2tLuKvr4jcCTzNNMpQDUer72MMmzA==", + "dev": true, + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@web/dev-server-core/node_modules/es-module-lexer": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz", + "integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==", + "dev": true + }, + "node_modules/@web/dev-server-core/node_modules/lru-cache": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-8.0.5.tgz", + "integrity": "sha512-MhWWlVnuab1RG5/zMRRcVGXZLCXrZTgfwMikgzCegsPnG62yDQo5JnqKkrK4jO5iKqDAZGItAqN5CtKBCBWRUA==", + "dev": true, + "engines": { + "node": ">=16.14" + } + }, + "node_modules/@web/dev-server-core/node_modules/parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true + }, + "node_modules/@web/dev-server-core/node_modules/readdirp": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.0.2.tgz", + "integrity": "sha512-yDMz9g+VaZkqBYS/ozoBJwaBhTbZo3UNYQHNRw1D3UFQB8oHB4uS/tAODO+ZLjGWmUbKnIlOWO+aaIiAxrUWHA==", + "dev": true, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@web/dev-server-rollup": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/@web/dev-server-rollup/-/dev-server-rollup-0.6.4.tgz", + "integrity": "sha512-sJZfTGCCrdku5xYnQQG51odGI092hKY9YFM0X3Z0tRY3iXKXcYRaLZrErw5KfCxr6g0JRuhe4BBhqXTA5Q2I3Q==", + "dev": true, + "dependencies": { + "@rollup/plugin-node-resolve": "^15.0.1", + "@web/dev-server-core": "^0.7.2", + "nanocolors": "^0.2.1", + "parse5": "^6.0.1", + "rollup": "^4.4.0", + "whatwg-url": "^14.0.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@web/dev-server-rollup/node_modules/parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true + }, + "node_modules/@web/dev-server/node_modules/@web/config-loader": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@web/config-loader/-/config-loader-0.3.2.tgz", + "integrity": "sha512-Vrjv/FexBGmAdnCYpJKLHX1dfT1UaUdvHmX1JRaWos9OvDf/tFznYJ5SpJwww3Rl87/ewvLSYG7kfsMqEAsizQ==", + "dev": true, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@web/dev-server/node_modules/array-back": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-3.1.0.tgz", + "integrity": "sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/@web/dev-server/node_modules/command-line-args": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/command-line-args/-/command-line-args-5.2.1.tgz", + "integrity": "sha512-H4UfQhZyakIjC74I9d34fGYDwk3XpSr17QhEd0Q3I9Xq1CETHo4Hcuo87WyWHpAF1aSLjLRf5lD9ZGX2qStUvg==", + "dev": true, + "dependencies": { + "array-back": "^3.1.0", + "find-replace": "^3.0.0", + "lodash.camelcase": "^4.3.0", + "typical": "^4.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/@web/dev-server/node_modules/find-replace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-replace/-/find-replace-3.0.0.tgz", + "integrity": "sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ==", + "dev": true, + "dependencies": { + "array-back": "^3.0.1" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/@web/dev-server/node_modules/typical": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/typical/-/typical-4.0.0.tgz", + "integrity": "sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@web/parse5-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@web/parse5-utils/-/parse5-utils-2.1.0.tgz", + "integrity": "sha512-GzfK5disEJ6wEjoPwx8AVNwUe9gYIiwc+x//QYxYDAFKUp4Xb1OJAGLc2l2gVrSQmtPGLKrTRcW90Hv4pEq1qA==", + "dev": true, + "dependencies": { + "@types/parse5": "^6.0.1", + "parse5": "^6.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@web/parse5-utils/node_modules/parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true + }, + "node_modules/@web/test-runner": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@web/test-runner/-/test-runner-0.19.0.tgz", + "integrity": "sha512-qLUupi88OK1Kl52cWPD/2JewUCRUxYsZ1V1DyLd05P7u09zCdrUYrtkB/cViWyxlBe/TOvqkSNpcTv6zLJ9GoA==", + "dev": true, + "dependencies": { + "@web/browser-logs": "^0.4.0", + "@web/config-loader": "^0.3.0", + "@web/dev-server": "^0.4.0", + "@web/test-runner-chrome": "^0.17.0", + "@web/test-runner-commands": "^0.9.0", + "@web/test-runner-core": "^0.13.0", + "@web/test-runner-mocha": "^0.9.0", + "camelcase": "^6.2.0", + "command-line-args": "^5.1.1", + "command-line-usage": "^7.0.1", + "convert-source-map": "^2.0.0", + "diff": "^5.0.0", + "globby": "^11.0.1", + "nanocolors": "^0.2.1", + "portfinder": "^1.0.32", + "source-map": "^0.7.3" + }, + "bin": { + "web-test-runner": "dist/bin.js", + "wtr": "dist/bin.js" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@web/test-runner-chrome": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@web/test-runner-chrome/-/test-runner-chrome-0.17.0.tgz", + "integrity": "sha512-Il5N9z41NKWCrQM1TVgRaDWWYoJtG5Ha4fG+cN1MWL2OlzBS4WoOb4lFV3EylZ7+W3twZOFr1zy2Rx61yDYd/A==", + "dev": true, + "dependencies": { + "@web/test-runner-core": "^0.13.0", + "@web/test-runner-coverage-v8": "^0.8.0", + "async-mutex": "0.4.0", + "chrome-launcher": "^0.15.0", + "puppeteer-core": "^23.2.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@web/test-runner-commands": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@web/test-runner-commands/-/test-runner-commands-0.9.0.tgz", + "integrity": "sha512-zeLI6QdH0jzzJMDV5O42Pd8WLJtYqovgdt0JdytgHc0d1EpzXDsc7NTCJSImboc2NcayIsWAvvGGeRF69SMMYg==", + "dev": true, + "dependencies": { + "@web/test-runner-core": "^0.13.0", + "mkdirp": "^1.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@web/test-runner-core": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/@web/test-runner-core/-/test-runner-core-0.13.4.tgz", + "integrity": "sha512-84E1025aUSjvZU1j17eCTwV7m5Zg3cZHErV3+CaJM9JPCesZwLraIa0ONIQ9w4KLgcDgJFw9UnJ0LbFf42h6tg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.12.11", + "@types/babel__code-frame": "^7.0.2", + "@types/co-body": "^6.1.0", + "@types/convert-source-map": "^2.0.0", + "@types/debounce": "^1.2.0", + "@types/istanbul-lib-coverage": "^2.0.3", + "@types/istanbul-reports": "^3.0.0", + "@web/browser-logs": "^0.4.0", + "@web/dev-server-core": "^0.7.3", + "chokidar": "^4.0.1", + "cli-cursor": "^3.1.0", + "co-body": "^6.1.0", + "convert-source-map": "^2.0.0", + "debounce": "^1.2.0", + "dependency-graph": "^0.11.0", + "globby": "^11.0.1", + "internal-ip": "^6.2.0", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-report": "^3.0.1", + "istanbul-reports": "^3.0.2", + "log-update": "^4.0.0", + "nanocolors": "^0.2.1", + "nanoid": "^3.1.25", + "open": "^8.0.2", + "picomatch": "^2.2.2", + "source-map": "^0.7.3" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@web/test-runner-core/node_modules/chokidar": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.1.tgz", + "integrity": "sha512-n8enUVCED/KVRQlab1hr3MVpcVMvxtZjmEa956u+4YijlmQED223XMSYj2tLuKvr4jcCTzNNMpQDUer72MMmzA==", + "dev": true, + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@web/test-runner-core/node_modules/readdirp": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.0.2.tgz", + "integrity": "sha512-yDMz9g+VaZkqBYS/ozoBJwaBhTbZo3UNYQHNRw1D3UFQB8oHB4uS/tAODO+ZLjGWmUbKnIlOWO+aaIiAxrUWHA==", + "dev": true, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@web/test-runner-coverage-v8": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@web/test-runner-coverage-v8/-/test-runner-coverage-v8-0.8.0.tgz", + "integrity": "sha512-PskiucYpjUtgNfR2zF2AWqWwjXL7H3WW/SnCAYmzUrtob7X9o/+BjdyZ4wKbOxWWSbJO4lEdGIDLu+8X2Xw+lA==", + "dev": true, + "dependencies": { + "@web/test-runner-core": "^0.13.0", + "istanbul-lib-coverage": "^3.0.0", + "lru-cache": "^8.0.4", + "picomatch": "^2.2.2", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@web/test-runner-coverage-v8/node_modules/lru-cache": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-8.0.5.tgz", + "integrity": "sha512-MhWWlVnuab1RG5/zMRRcVGXZLCXrZTgfwMikgzCegsPnG62yDQo5JnqKkrK4jO5iKqDAZGItAqN5CtKBCBWRUA==", + "dev": true, + "engines": { + "node": ">=16.14" + } + }, + "node_modules/@web/test-runner-mocha": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@web/test-runner-mocha/-/test-runner-mocha-0.9.0.tgz", + "integrity": "sha512-ZL9F6FXd0DBQvo/h/+mSfzFTSRVxzV9st/AHhpgABtUtV/AIpVE9to6+xdkpu6827kwjezdpuadPfg+PlrBWqQ==", + "dev": true, + "dependencies": { + "@web/test-runner-core": "^0.13.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@web/test-runner-playwright": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@web/test-runner-playwright/-/test-runner-playwright-0.11.0.tgz", + "integrity": "sha512-s+f43DSAcssKYVOD9SuzueUcctJdHzq1by45gAnSCKa9FQcaTbuYe8CzmxA21g+NcL5+ayo4z+MA9PO4H+PssQ==", + "dev": true, + "dependencies": { + "@web/test-runner-core": "^0.13.0", + "@web/test-runner-coverage-v8": "^0.8.0", + "playwright": "^1.22.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@web/test-runner/node_modules/@web/config-loader": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@web/config-loader/-/config-loader-0.3.2.tgz", + "integrity": "sha512-Vrjv/FexBGmAdnCYpJKLHX1dfT1UaUdvHmX1JRaWos9OvDf/tFznYJ5SpJwww3Rl87/ewvLSYG7kfsMqEAsizQ==", + "dev": true, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@web/test-runner/node_modules/array-back": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-3.1.0.tgz", + "integrity": "sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/@web/test-runner/node_modules/command-line-args": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/command-line-args/-/command-line-args-5.2.1.tgz", + "integrity": "sha512-H4UfQhZyakIjC74I9d34fGYDwk3XpSr17QhEd0Q3I9Xq1CETHo4Hcuo87WyWHpAF1aSLjLRf5lD9ZGX2qStUvg==", + "dev": true, + "dependencies": { + "array-back": "^3.1.0", + "find-replace": "^3.0.0", + "lodash.camelcase": "^4.3.0", + "typical": "^4.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/@web/test-runner/node_modules/find-replace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-replace/-/find-replace-3.0.0.tgz", + "integrity": "sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ==", + "dev": true, + "dependencies": { + "array-back": "^3.0.1" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/@web/test-runner/node_modules/typical": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/typical/-/typical-4.0.0.tgz", + "integrity": "sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@xmldom/xmldom": { + "version": "0.8.10", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz", + "integrity": "sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==", + "dev": true, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@zebra-fed/zeta-icons": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@zebra-fed/zeta-icons/-/zeta-icons-0.9.0.tgz", + "integrity": "sha512-OgGaAY+VeZ7AekrF8BzrXqe6To/xYjGGGUHk6f0ORWkJK/18vOMqTGJZktgthh0X27Cl8NzNaG/NILtMrv4KNw==" + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "dev": true, + "optional": true, + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "dev": true, + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-escapes/node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/append-transform": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-2.0.0.tgz", + "integrity": "sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==", + "dev": true, + "dependencies": { + "default-require-extensions": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/archy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", + "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==", + "dev": true + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true, + "optional": true + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dev": true, + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/array-back": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-6.2.2.tgz", + "integrity": "sha512-gUAZ7HPyb4SJczXAMUXMGAvI976JoK3qEx9v1FTmeYuJj0IBiaKttG1ydtGKdkfqWkIkouke7nG8ufGy77+Cvw==", + "dev": true, + "engines": { + "node": ">=12.17" + } + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", + "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz", + "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.2.1", + "get-intrinsic": "^1.2.3", + "is-array-buffer": "^3.0.4", + "is-shared-array-buffer": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/ast-types": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", + "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", + "dev": true, + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/async": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "dev": true, + "dependencies": { + "lodash": "^4.17.14" + } + }, + "node_modules/async-mutex": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.4.0.tgz", + "integrity": "sha512-eJFZ1YhRR8UN8eBLoNzcDPcy/jqjsg6I1AP+KvWQX80BqOSW1oJPJXDylPUEeMr2ZQvHgnQ//Lp6f3RQ1zI7HA==", + "dev": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/axe-core": { + "version": "4.10.2", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.10.2.tgz", + "integrity": "sha512-RE3mdQ7P3FRSe7eqCWoeQ/Z9QXrtniSjp1wUjt5nRC3WIpz5rSCve6o3fsZ2aCpJtrZjSZgjwXAoTO5k4tEI0w==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/axe-html-reporter": { + "version": "2.2.11", + "resolved": "https://registry.npmjs.org/axe-html-reporter/-/axe-html-reporter-2.2.11.tgz", + "integrity": "sha512-WlF+xlNVgNVWiM6IdVrsh+N0Cw7qupe5HT9N6Uyi+aN7f6SSi92RDomiP1noW8OWIV85V6x404m5oKMeqRV3tQ==", + "dev": true, + "dependencies": { + "mustache": "^4.0.1" + }, + "engines": { + "node": ">=8.9.0" + }, + "peerDependencies": { + "axe-core": ">=3" + } + }, + "node_modules/axe-playwright": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/axe-playwright/-/axe-playwright-2.0.3.tgz", + "integrity": "sha512-s7iI2okyHHsD3XZK4RMJtTy2UASkNWLQtnzLuaHiK3AWkERf+cqZJqkxb7O4b56fnbib9YnZVRByTl92ME3o6g==", + "dev": true, + "dependencies": { + "@types/junit-report-builder": "^3.0.2", + "axe-core": "^4.10.0", + "axe-html-reporter": "2.2.11", + "junit-report-builder": "^5.1.1", + "picocolors": "^1.1.0" + }, + "peerDependencies": { + "playwright": ">1.0.0" + } + }, + "node_modules/axios": { + "version": "1.7.8", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.8.tgz", + "integrity": "sha512-Uu0wb7KNqK2t5K+YQyVCLM76prD5sRFjKHbJYCP1J7JFGEQ6nN7HWn9+04LAeiJ3ji54lgS/gZCH1oxyrf1SPw==", + "dev": true, + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/b4a": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.7.tgz", + "integrity": "sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==", + "dev": true + }, + "node_modules/babel-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", + "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", + "dev": true, + "dependencies": { + "@jest/transform": "^29.7.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.6.3", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/babel-jest/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", + "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "dev": true, + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz", + "integrity": "sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw==", + "dev": true, + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/babel-preset-jest": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", + "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "dev": true, + "dependencies": { + "babel-plugin-jest-hoist": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/bare-events": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.5.0.tgz", + "integrity": "sha512-/E8dDe9dsbLyh2qrZ64PEPadOQ0F4gbl1sUJOrmph7xOiIxfY8vwab/4bFLh4Y88/Hk/ujKcrQKc+ps0mv873A==", + "dev": true, + "optional": true + }, + "node_modules/bare-fs": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-2.3.5.tgz", + "integrity": "sha512-SlE9eTxifPDJrT6YgemQ1WGFleevzwY+XAP1Xqgl56HtcrisC2CHCZ2tq6dBpcH2TnNxwUEUGhweo+lrQtYuiw==", + "dev": true, + "optional": true, + "dependencies": { + "bare-events": "^2.0.0", + "bare-path": "^2.0.0", + "bare-stream": "^2.0.0" + } + }, + "node_modules/bare-os": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-2.4.4.tgz", + "integrity": "sha512-z3UiI2yi1mK0sXeRdc4O1Kk8aOa/e+FNWZcTiPB/dfTWyLypuE99LibgRaQki914Jq//yAWylcAt+mknKdixRQ==", + "dev": true, + "optional": true + }, + "node_modules/bare-path": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-2.1.3.tgz", + "integrity": "sha512-lh/eITfU8hrj9Ru5quUp0Io1kJWIk1bTjzo7JH1P5dWmQ2EL4hFUlfI8FonAhSlgIfhn63p84CDY/x+PisgcXA==", + "dev": true, + "optional": true, + "dependencies": { + "bare-os": "^2.1.0" + } + }, + "node_modules/bare-stream": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.4.2.tgz", + "integrity": "sha512-XZ4ln/KV4KT+PXdIWTKjsLY+quqCaEtqqtgGJVPw9AoM73By03ij64YjepK0aQvHSWDb6AfAZwqKaFu68qkrdA==", + "dev": true, + "optional": true, + "dependencies": { + "streamx": "^2.20.0" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/basic-ftp": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.5.tgz", + "integrity": "sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==", + "dev": true, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/better-opn": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/better-opn/-/better-opn-3.0.2.tgz", + "integrity": "sha512-aVNobHnJqLiUelTaHat9DZ1qM2w0C0Eym4LPI/3JxOnSokGVdsl1T1kN7TFvsEAD8G47A6VKQ0TVHqbBnYMJlQ==", + "dev": true, + "dependencies": { + "open": "^8.0.4" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browser-assert": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/browser-assert/-/browser-assert-1.2.1.tgz", + "integrity": "sha512-nfulgvOR6S4gt9UKCeGJOuSGBPGiFT6oQ/2UBnvTY/5aQ1PnksW72fhZkM30DzoRRv2WpwZf1vHHEr3mtuXIWQ==", + "dev": true + }, + "node_modules/browserslist": { + "version": "4.24.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.2.tgz", + "integrity": "sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001669", + "electron-to-chromium": "^1.5.41", + "node-releases": "^2.0.18", + "update-browserslist-db": "^1.1.1" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cache-content-type": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-content-type/-/cache-content-type-1.0.1.tgz", + "integrity": "sha512-IKufZ1o4Ut42YUrZSo8+qnMTrFuKkvyoLXUywKz9GJ5BrhOFGhLdkx9sG4KAnVvbY6kEcSFjLQul+DVmBm2bgA==", + "dev": true, + "dependencies": { + "mime-types": "^2.1.18", + "ylru": "^1.2.0" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/caching-transform": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", + "integrity": "sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==", + "dev": true, + "dependencies": { + "hasha": "^5.0.0", + "make-dir": "^3.0.0", + "package-hash": "^4.0.0", + "write-file-atomic": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/caching-transform/node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/caching-transform/node_modules/write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, + "node_modules/call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001686", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001686.tgz", + "integrity": "sha512-Y7deg0Aergpa24M3qLC5xjNklnKnhsmSyR/V89dLZ1n0ucJIFNs7PgR2Yfa/Zf6W79SbBicgtGxZr2juHkEUIA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/cem-plugin-better-lit-types": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/cem-plugin-better-lit-types/-/cem-plugin-better-lit-types-0.2.1.tgz", + "integrity": "sha512-uMDvNcWAjfC2rxsfFK34JnwIrUtq0uuTB7nJ+MdEfrsr+vA74GgaIThJ2lz40vCsqSEepZgpvFUKwH9VoIUwGw==", + "dev": true, + "optionalDependencies": { + "@open-wc/lit-helpers": "0.5.1", + "lit": "^2.6.1", + "typescript": "^4.9.4", + "typescript-json-schema": "^0.55.0" + } + }, + "node_modules/cem-plugin-better-lit-types/node_modules/@lit/reactive-element": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-1.6.3.tgz", + "integrity": "sha512-QuTgnG52Poic7uM1AN5yJ09QMe0O28e10XzSvWDz02TJiiKee4stsiownEIadWm8nYzyDAyT+gKzUoZmiWQtsQ==", + "dev": true, + "optional": true, + "dependencies": { + "@lit-labs/ssr-dom-shim": "^1.0.0" + } + }, + "node_modules/cem-plugin-better-lit-types/node_modules/@open-wc/lit-helpers": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@open-wc/lit-helpers/-/lit-helpers-0.5.1.tgz", + "integrity": "sha512-r/PJpYueX2CX6R/20K+1P9jtMbf6g/O076qCpqh/o6XrThJahClpbxbr8lhu0tCWIQ/SNFvLtIOmIRR7qobwWg==", + "dev": true, + "optional": true, + "peerDependencies": { + "lit": "^2.0.0" + } + }, + "node_modules/cem-plugin-better-lit-types/node_modules/lit": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/lit/-/lit-2.8.0.tgz", + "integrity": "sha512-4Sc3OFX9QHOJaHbmTMk28SYgVxLN3ePDjg7hofEft2zWlehFL3LiAuapWc4U/kYwMYJSh2hTCPZ6/LIC7ii0MA==", + "dev": true, + "optional": true, + "dependencies": { + "@lit/reactive-element": "^1.6.0", + "lit-element": "^3.3.0", + "lit-html": "^2.8.0" + } + }, + "node_modules/cem-plugin-better-lit-types/node_modules/lit-element": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-3.3.3.tgz", + "integrity": "sha512-XbeRxmTHubXENkV4h8RIPyr8lXc+Ff28rkcQzw3G6up2xg5E8Zu1IgOWIwBLEQsu3cOVFqdYwiVi0hv0SlpqUA==", + "dev": true, + "optional": true, + "dependencies": { + "@lit-labs/ssr-dom-shim": "^1.1.0", + "@lit/reactive-element": "^1.3.0", + "lit-html": "^2.8.0" + } + }, + "node_modules/cem-plugin-better-lit-types/node_modules/lit-html": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-2.8.0.tgz", + "integrity": "sha512-o9t+MQM3P4y7M7yNzqAyjp7z+mQGa4NS4CxiyLqFPyFWyc4O+nodLrkrxSaCTrla6M5YOLaT3RpbbqjszB5g3Q==", + "dev": true, + "optional": true, + "dependencies": { + "@types/trusted-types": "^2.0.2" + } + }, + "node_modules/cem-plugin-better-lit-types/node_modules/typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "dev": true, + "optional": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/cem-plugin-custom-jsdoc-tags": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/cem-plugin-custom-jsdoc-tags/-/cem-plugin-custom-jsdoc-tags-1.1.2.tgz", + "integrity": "sha512-vYermZIrZ8UulEN19LGPkbfwmqES1M8rzq6I8rem2AnM1E5o0EI3PNkRz/fDDOPwCdf5qusFhIlbg+UG7TcKYw==", + "dev": true + }, + "node_modules/chai": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.2.tgz", + "integrity": "sha512-aGtmf24DW6MLHHG5gCx4zaI3uBq3KRtxeVs0DjFH6Z0rDNbsvTxFASFvdj79pxjxZ8/5u3PIiN3IwEIQkiiuPw==", + "dev": true, + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/chai-a11y-axe": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/chai-a11y-axe/-/chai-a11y-axe-1.5.0.tgz", + "integrity": "sha512-V/Vg/zJDr9aIkaHJ2KQu7lGTQQm5ZOH4u1k5iTMvIXuSVlSuUo0jcSpSqf9wUn9zl6oQXa4e4E0cqH18KOgKlQ==", + "dev": true, + "dependencies": { + "axe-core": "^4.3.3" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk-template": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/chalk-template/-/chalk-template-0.4.0.tgz", + "integrity": "sha512-/ghrgmhfY8RaSdeo43hNXxpoHAtxdbskUHjPpfqUWGttFgycUhYPGx3YZBCnUCvOa7Doivn1IZec3DEGFoMgLg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/chalk-template?sponsor=1" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/check-error": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", + "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", + "dev": true, + "engines": { + "node": ">= 16" + } + }, + "node_modules/chokidar": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz", + "integrity": "sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==", + "dev": true, + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chrome-launcher": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-0.15.2.tgz", + "integrity": "sha512-zdLEwNo3aUVzIhKhTtXfxhdvZhUghrnmkvcAq2NoDd+LeOHKf03H5jwZ8T/STsAlzyALkBVK552iaG1fGf1xVQ==", + "dev": true, + "dependencies": { + "@types/node": "*", + "escape-string-regexp": "^4.0.0", + "is-wsl": "^2.2.0", + "lighthouse-logger": "^1.0.0" + }, + "bin": { + "print-chrome-path": "bin/print-chrome-path.js" + }, + "engines": { + "node": ">=12.13.0" + } + }, + "node_modules/chromium-bidi": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.8.0.tgz", + "integrity": "sha512-uJydbGdTw0DEUjhoogGveneJVWX/9YuqkWePzMmkBYwtdAqo5d3J/ovNKFr+/2hWXYmYCr6it8mSSTIj6SS6Ug==", + "dev": true, + "dependencies": { + "mitt": "3.0.1", + "urlpattern-polyfill": "10.0.0", + "zod": "3.23.8" + }, + "peerDependencies": { + "devtools-protocol": "*" + } + }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.1.tgz", + "integrity": "sha512-cuSVIHi9/9E/+821Qjdvngor+xpnlwnuwIyZOaLmHBVdXL+gP+I6QQB9VkO7RI77YIcTV+S1W9AreJ5eN63JBA==", + "dev": true + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", + "dev": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/co-body": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/co-body/-/co-body-6.2.0.tgz", + "integrity": "sha512-Kbpv2Yd1NdL1V/V4cwLVxraHDV6K8ayohr2rmH0J87Er8+zJjcTa6dAn9QMPC9CRgU8+aNajKbSf1TzDB1yKPA==", + "dev": true, + "dependencies": { + "@hapi/bourne": "^3.0.0", + "inflation": "^2.0.0", + "qs": "^6.5.2", + "raw-body": "^2.3.3", + "type-is": "^1.6.16" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/code-block-writer": { + "version": "13.0.3", + "resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-13.0.3.tgz", + "integrity": "sha512-Oofo0pq3IKnsFtuHqSF7TqBfr71aeyZDVJ0HpmqB7FBM2qEigL0iPONSCZSO9pE9dZTAxANe5XHG9Uy0YMv8cg==", + "dev": true + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", + "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", + "dev": true + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/command-line-args": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/command-line-args/-/command-line-args-6.0.1.tgz", + "integrity": "sha512-Jr3eByUjqyK0qd8W0SGFW1nZwqCaNCtbXjRo2cRJC1OYxWl3MZ5t1US3jq+cO4sPavqgw4l9BMGX0CBe+trepg==", + "dev": true, + "dependencies": { + "array-back": "^6.2.2", + "find-replace": "^5.0.2", + "lodash.camelcase": "^4.3.0", + "typical": "^7.2.0" + }, + "engines": { + "node": ">=12.20" + }, + "peerDependencies": { + "@75lb/nature": "latest" + }, + "peerDependenciesMeta": { + "@75lb/nature": { + "optional": true + } + } + }, + "node_modules/command-line-usage": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/command-line-usage/-/command-line-usage-7.0.3.tgz", + "integrity": "sha512-PqMLy5+YGwhMh1wS04mVG44oqDsgyLRSKJBdOo1bnYhMKBW65gZF1dRp2OZRhiTjgUHljy99qkO7bsctLaw35Q==", + "dev": true, + "dependencies": { + "array-back": "^6.2.2", + "chalk-template": "^0.4.0", + "table-layout": "^4.1.0", + "typical": "^7.1.1" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/commander": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", + "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/comment-parser": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.2.4.tgz", + "integrity": "sha512-pm0b+qv+CkWNriSTMsfnjChF9kH0kxz55y44Wo5le9qLxMj5xDQAaEd9ZN1ovSuk9CsrncWaFwgpOMg7ClJwkw==", + "dev": true, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dev": true, + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/cookies": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/cookies/-/cookies-0.9.1.tgz", + "integrity": "sha512-TG2hpqe4ELx54QER/S3HQ9SRVnQnGBtKUz5bLQWtYAQ+o6GpgMs6sYUvaiJjVxb+UXwhRhAEP3m7LbsIZ77Hmw==", + "dev": true, + "dependencies": { + "depd": "~2.0.0", + "keygrip": "~1.1.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/create-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", + "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "prompts": "^2.0.1" + }, + "bin": { + "create-jest": "bin/create-jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true, + "optional": true + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", + "dev": true + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "dev": true, + "peer": true + }, + "node_modules/custom-elements-manifest": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/custom-elements-manifest/-/custom-elements-manifest-1.0.0.tgz", + "integrity": "sha512-j59k0ExGCKA8T6Mzaq+7axc+KVHwpEphEERU7VZ99260npu/p/9kd+Db+I3cGKxHkM5y6q5gnlXn00mzRQkX2A==", + "dev": true + }, + "node_modules/cwd": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/cwd/-/cwd-0.10.0.tgz", + "integrity": "sha512-YGZxdTTL9lmLkCUTpg4j0zQ7IhRB5ZmqNBbGCl3Tg6MP/d5/6sY7L5mmTjzbc6JKgVZYiqTQTNhPFsbXNGlRaA==", + "dev": true, + "dependencies": { + "find-pkg": "^0.1.2", + "fs-exists-sync": "^0.1.0" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/data-uri-to-buffer": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", + "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", + "dev": true, + "engines": { + "node": ">= 14" + } + }, + "node_modules/data-view-buffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", + "integrity": "sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz", + "integrity": "sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz", + "integrity": "sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/debounce": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz", + "integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==", + "dev": true + }, + "node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/dedent": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz", + "integrity": "sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==", + "dev": true, + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/deep-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", + "integrity": "sha512-bHtC0iYvWhyaTzvV3CZgPeZQqCOBGyGsVV7v4eevpdkLHfiSrXUdBG+qAuSz4RI70sszvjQ1QSZ98An1yNwpSw==", + "dev": true + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/default-gateway": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz", + "integrity": "sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==", + "dev": true, + "dependencies": { + "execa": "^5.0.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/default-require-extensions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.1.tgz", + "integrity": "sha512-eXTJmRbm2TIt9MgWTsOH1wEuhew6XGZcMeGKCtLedIg/NCsg1iBePXkceTdK4Fii7pzmN9tGsZhKzZ4h7O/fxw==", + "dev": true, + "dependencies": { + "strip-bom": "^4.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/degenerator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", + "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", + "dev": true, + "dependencies": { + "ast-types": "^0.13.4", + "escodegen": "^2.1.0", + "esprima": "^4.0.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/del": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/del/-/del-8.0.0.tgz", + "integrity": "sha512-R6ep6JJ+eOBZsBr9esiNN1gxFbZE4Q2cULkUSFumGYecAiS6qodDvcPx/sFuWHMNul7DWmrtoEOpYSm7o6tbSA==", + "dev": true, + "dependencies": { + "globby": "^14.0.2", + "is-glob": "^4.0.3", + "is-path-cwd": "^3.0.0", + "is-path-inside": "^4.0.0", + "p-map": "^7.0.2", + "slash": "^5.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/del/node_modules/globby": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/globby/-/globby-14.0.2.tgz", + "integrity": "sha512-s3Fq41ZVh7vbbe2PN3nrW7yC7U7MFVc5c98/iTl9c2GawNMKx/J648KQRW6WKkuU8GIbbh2IXfIRQjOZnXcTnw==", + "dev": true, + "dependencies": { + "@sindresorhus/merge-streams": "^2.1.0", + "fast-glob": "^3.3.2", + "ignore": "^5.2.4", + "path-type": "^5.0.0", + "slash": "^5.1.0", + "unicorn-magic": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/del/node_modules/path-type": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-5.0.0.tgz", + "integrity": "sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "dev": true + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/dependency-graph": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-0.11.0.tgz", + "integrity": "sha512-JeMq7fEshyepOWDfcfHK06N3MhyPhz++vtqWhMT5O9A3K42rdsEDpfdVqjaqaAhsw6a+ZqeDvQVtD0hFHQWrzg==", + "dev": true, + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "dev": true, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/devtools-protocol": { + "version": "0.0.1367902", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1367902.tgz", + "integrity": "sha512-XxtPuC3PGakY6PD7dG66/o8KwJ/LkH2/EKe19Dcw58w53dv4/vSQEkn/SzuyhHE2q4zPgCkxQBxus3VV4ql+Pg==", + "dev": true + }, + "node_modules/didyoumean2": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/didyoumean2/-/didyoumean2-4.1.0.tgz", + "integrity": "sha512-qTBmfQoXvhKO75D/05C8m+fteQmn4U46FWYiLhXtZQInzitXLWY0EQ/2oKnpAz9g2lQWW8jYcLcT+hPJGT+kig==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.10.2", + "leven": "^3.1.0", + "lodash.deburr": "^4.1.0" + }, + "engines": { + "node": ">=10.13" + } + }, + "node_modules/diff": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/diffable-html": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/diffable-html/-/diffable-html-4.1.0.tgz", + "integrity": "sha512-++kyNek+YBLH8cLXS+iTj/Hiy2s5qkRJEJ8kgu/WHbFrVY2vz9xPFUT+fii2zGF0m1CaojDlQJjkfrCt7YWM1g==", + "dev": true, + "dependencies": { + "htmlparser2": "^3.9.2" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", + "dev": true + }, + "node_modules/dom-serializer": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", + "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==", + "dev": true, + "dependencies": { + "domelementtype": "^2.0.1", + "entities": "^2.0.0" + } + }, + "node_modules/dom-serializer/node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ] + }, + "node_modules/dom-serializer/node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "dev": true, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/dom5": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dom5/-/dom5-3.0.1.tgz", + "integrity": "sha512-JPFiouQIr16VQ4dX6i0+Hpbg3H2bMKPmZ+WZgBOSSvOPx9QHwwY8sPzeM2baUtViESYto6wC2nuZOMC/6gulcA==", + "dev": true, + "dependencies": { + "@types/parse5": "^2.2.34", + "clone": "^2.1.0", + "parse5": "^4.0.0" + } + }, + "node_modules/dom5/node_modules/@types/parse5": { + "version": "2.2.34", + "resolved": "https://registry.npmjs.org/@types/parse5/-/parse5-2.2.34.tgz", + "integrity": "sha512-p3qOvaRsRpFyEmaS36RtLzpdxZZnmxGuT1GMgzkTtTJVFuEw7KFjGK83MFODpJExgX1bEzy9r0NYjMC3IMfi7w==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/dom5/node_modules/parse5": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-4.0.0.tgz", + "integrity": "sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA==", + "dev": true + }, + "node_modules/domelementtype": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", + "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==", + "dev": true + }, + "node_modules/domhandler": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", + "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", + "dev": true, + "dependencies": { + "domelementtype": "1" + } + }, + "node_modules/domutils": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", + "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", + "dev": true, + "dependencies": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "dev": true + }, + "node_modules/electron-to-chromium": { + "version": "1.5.68", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.68.tgz", + "integrity": "sha512-FgMdJlma0OzUYlbrtZ4AeXjKxKPk6KT8WOP8BjcqxWtlg8qyJQjRzPJzUtUn5GBg1oQ26hFs7HOOHJMYiJRnvQ==", + "dev": true + }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", + "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", + "dev": true + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/entities": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", + "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==", + "dev": true + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/errorstacks": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/errorstacks/-/errorstacks-2.4.1.tgz", + "integrity": "sha512-jE4i0SMYevwu/xxAuzhly/KTwtj0xDhbzB6m1xPImxTkw8wcCbgarOQPfCVMi5JKVyW7in29pNJCCJrry3Ynnw==", + "dev": true + }, + "node_modules/es-abstract": { + "version": "1.23.5", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.5.tgz", + "integrity": "sha512-vlmniQ0WNPwXqA0BnmwV3Ng7HxiGlh6r5U6JcTMNx8OilcAGqVJBHJcPjqOMaczU9fRuRK5Px2BdVyPRnKMMVQ==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "arraybuffer.prototype.slice": "^1.0.3", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "data-view-buffer": "^1.0.1", + "data-view-byte-length": "^1.0.1", + "data-view-byte-offset": "^1.0.0", + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-set-tostringtag": "^2.0.3", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.6", + "get-intrinsic": "^1.2.4", + "get-symbol-description": "^1.0.2", + "globalthis": "^1.0.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.0.3", + "has-symbols": "^1.0.3", + "hasown": "^2.0.2", + "internal-slot": "^1.0.7", + "is-array-buffer": "^3.0.4", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.1", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.3", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.13", + "is-weakref": "^1.0.2", + "object-inspect": "^1.13.3", + "object-keys": "^1.1.1", + "object.assign": "^4.1.5", + "regexp.prototype.flags": "^1.5.3", + "safe-array-concat": "^1.1.2", + "safe-regex-test": "^1.0.3", + "string.prototype.trim": "^1.2.9", + "string.prototype.trimend": "^1.0.8", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.2", + "typed-array-byte-length": "^1.0.1", + "typed-array-byte-offset": "^1.0.2", + "typed-array-length": "^1.0.6", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.15" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", + "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==", + "dev": true + }, + "node_modules/es-object-atoms": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", + "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", + "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.4", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-to-primitive": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", + "dev": true, + "dependencies": { + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "dev": true + }, + "node_modules/esbuild": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.24.0.tgz", + "integrity": "sha512-FuLPevChGDshgSicjisSooU0cemp/sGXR841D5LHMB7mTVOmsEHcAxaH3irL53+8YDIeVNQEySh4DaYU/iuPqQ==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.24.0", + "@esbuild/android-arm": "0.24.0", + "@esbuild/android-arm64": "0.24.0", + "@esbuild/android-x64": "0.24.0", + "@esbuild/darwin-arm64": "0.24.0", + "@esbuild/darwin-x64": "0.24.0", + "@esbuild/freebsd-arm64": "0.24.0", + "@esbuild/freebsd-x64": "0.24.0", + "@esbuild/linux-arm": "0.24.0", + "@esbuild/linux-arm64": "0.24.0", + "@esbuild/linux-ia32": "0.24.0", + "@esbuild/linux-loong64": "0.24.0", + "@esbuild/linux-mips64el": "0.24.0", + "@esbuild/linux-ppc64": "0.24.0", + "@esbuild/linux-riscv64": "0.24.0", + "@esbuild/linux-s390x": "0.24.0", + "@esbuild/linux-x64": "0.24.0", + "@esbuild/netbsd-x64": "0.24.0", + "@esbuild/openbsd-arm64": "0.24.0", + "@esbuild/openbsd-x64": "0.24.0", + "@esbuild/sunos-x64": "0.24.0", + "@esbuild/win32-arm64": "0.24.0", + "@esbuild/win32-ia32": "0.24.0", + "@esbuild/win32-x64": "0.24.0" + } + }, + "node_modules/esbuild-register": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/esbuild-register/-/esbuild-register-3.6.0.tgz", + "integrity": "sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg==", + "dev": true, + "dependencies": { + "debug": "^4.3.4" + }, + "peerDependencies": { + "esbuild": ">=0.12 <1" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "dev": true + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "dev": true, + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/escodegen/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint": { + "version": "9.16.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.16.0.tgz", + "integrity": "sha512-whp8mSQI4C8VXd+fLgSM0lh3UlmcFtVwUQjyKCFfsp+2ItAIYhlq/hqGahGqHE6cv9unM41VlqKk2VtKYR2TaA==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.19.0", + "@eslint/core": "^0.9.0", + "@eslint/eslintrc": "^3.2.0", + "@eslint/js": "9.16.0", + "@eslint/plugin-kit": "^0.2.3", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.1", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.5", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.2.0", + "eslint-visitor-keys": "^4.2.0", + "espree": "^10.3.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-config-prettier": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz", + "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==", + "dev": true, + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-formatter-unix": { + "version": "8.40.0", + "resolved": "https://registry.npmjs.org/eslint-formatter-unix/-/eslint-formatter-unix-8.40.0.tgz", + "integrity": "sha512-gfsmFZ/cb1MobrMfYl2IPFLZEz2tWQVO/tnmziNQdhWJMN85GfZD64dcPsEgaEoeVKgAtK6W9LWLlOxhJWZvDw==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/eslint-plugin-lit": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-lit/-/eslint-plugin-lit-1.15.0.tgz", + "integrity": "sha512-Yhr2MYNz6Ln8megKcX503aVZQln8wsywCG49g0heiJ/Qr5UjkE4pGr4Usez2anNcc7NvlvHbQWMYwWcgH3XRKA==", + "dev": true, + "dependencies": { + "parse5": "^6.0.1", + "parse5-htmlparser2-tree-adapter": "^6.0.1", + "requireindex": "^1.2.0" + }, + "engines": { + "node": ">= 12" + }, + "peerDependencies": { + "eslint": ">= 5" + } + }, + "node_modules/eslint-plugin-lit-a11y": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-lit-a11y/-/eslint-plugin-lit-a11y-4.1.4.tgz", + "integrity": "sha512-u39vE1KNOzO99Nrz51oVGY0Iauzf59l9tZgBluE/cU1l86X9/peBMQHUAeGC536dlV4acFYj5yq/VLPsalvnzA==", + "dev": true, + "dependencies": { + "@thepassle/axobject-query": "^4.0.0", + "aria-query": "^5.1.3", + "axe-core": "^4.3.3", + "dom5": "^3.0.1", + "emoji-regex": "^10.2.1", + "eslint-plugin-lit": "^1.10.1", + "eslint-rule-extender": "0.0.1", + "language-tags": "^1.0.5", + "parse5": "^7.1.2", + "parse5-htmlparser2-tree-adapter": "^6.0.1", + "requireindex": "~1.2.0" + }, + "peerDependencies": { + "eslint": ">= 5" + } + }, + "node_modules/eslint-plugin-lit/node_modules/parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true + }, + "node_modules/eslint-plugin-storybook": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-storybook/-/eslint-plugin-storybook-0.11.1.tgz", + "integrity": "sha512-yGKpAYkBm/Q2hZg476vRUAvd9lAccjjSvzU5nYy3BSQbKTPy7uopx7JEpwk2vSuw4weTMZzWF64z9/gp/K5RCg==", + "dev": true, + "dependencies": { + "@storybook/csf": "^0.1.11", + "@typescript-eslint/utils": "^8.8.1", + "ts-dedent": "^2.2.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "eslint": ">=6" + } + }, + "node_modules/eslint-plugin-tsdoc": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-tsdoc/-/eslint-plugin-tsdoc-0.4.0.tgz", + "integrity": "sha512-MT/8b4aKLdDClnS8mP3R/JNjg29i0Oyqd/0ym6NnQf+gfKbJJ4ZcSh2Bs1H0YiUMTBwww5JwXGTWot/RwyJ7aQ==", + "dev": true, + "dependencies": { + "@microsoft/tsdoc": "0.15.1", + "@microsoft/tsdoc-config": "0.17.1" + } + }, + "node_modules/eslint-plugin-wc": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-wc/-/eslint-plugin-wc-2.2.0.tgz", + "integrity": "sha512-kjPp+aXz23fOl0JZJOJS+6adwhEv98KjZ2FJqWpc4vtmk4Oenz/JJmmNZrGSARgtyR0BLIF/kVWC6GSlHA+5MA==", + "dev": true, + "dependencies": { + "is-valid-element-name": "^1.0.0", + "js-levenshtein-esm": "^1.2.0" + }, + "peerDependencies": { + "eslint": ">=8.40.0" + } + }, + "node_modules/eslint-rule-extender": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/eslint-rule-extender/-/eslint-rule-extender-0.0.1.tgz", + "integrity": "sha512-F0j1Twve3lamL3J0rRSVAynlp58sDPG39JFcQrM+u9Na7PmCgiPHNODh6YE9mduaGcsn3NBqbf6LZRj0cLr8Ng==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kaicataldo" + } + }, + "node_modules/eslint-scope": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz", + "integrity": "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/espree": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", + "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", + "dev": true, + "dependencies": { + "acorn": "^8.14.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expand-tilde": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-1.2.2.tgz", + "integrity": "sha512-rtmc+cjLZqnu9dSYosX9EWmSJhTwpACgJQTfj4hgg2JjOD/6SIQalZrt4a3aQeh++oNxkazcaxrhPUj6+g5G/Q==", + "dev": true, + "dependencies": { + "os-homedir": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "dev": true, + "dependencies": { + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/expect-playwright": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/expect-playwright/-/expect-playwright-0.8.0.tgz", + "integrity": "sha512-+kn8561vHAY+dt+0gMqqj1oY+g5xWrsuGMk4QGxotT2WS545nVqqjs37z6hrYfIuucwqthzwJfCJUEYqixyljg==", + "dev": true + }, + "node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, + "node_modules/extract-zip/node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fastq": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "dev": true, + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "dev": true, + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/avajs/find-cache-dir?sponsor=1" + } + }, + "node_modules/find-cache-dir/node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/find-file-up": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/find-file-up/-/find-file-up-0.1.3.tgz", + "integrity": "sha512-mBxmNbVyjg1LQIIpgO8hN+ybWBgDQK8qjht+EbrTCGmmPV/sc7RF1i9stPTD6bpvXZywBdrwRYxhSdJv867L6A==", + "dev": true, + "dependencies": { + "fs-exists-sync": "^0.1.0", + "resolve-dir": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/find-pkg": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/find-pkg/-/find-pkg-0.1.2.tgz", + "integrity": "sha512-0rnQWcFwZr7eO0513HahrWafsc3CTFioEB7DRiEYCUM/70QXSY8f3mCST17HXLcPvEhzH/Ty/Bxd72ZZsr/yvw==", + "dev": true, + "dependencies": { + "find-file-up": "^0.1.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/find-process": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/find-process/-/find-process-1.4.7.tgz", + "integrity": "sha512-/U4CYp1214Xrp3u3Fqr9yNynUrr5Le4y0SsJh2lMDDSbpwYSz3M2SMWQC+wqcx79cN8PQtHQIL8KnuY9M66fdg==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "commander": "^5.1.0", + "debug": "^4.1.1" + }, + "bin": { + "find-process": "bin/find-process.js" + } + }, + "node_modules/find-replace": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/find-replace/-/find-replace-5.0.2.tgz", + "integrity": "sha512-Y45BAiE3mz2QsrN2fb5QEtO4qb44NcS7en/0y9PEVsg351HsLeVclP8QPMH79Le9sH3rs5RSwJu99W0WPZO43Q==", + "dev": true, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@75lb/nature": "latest" + }, + "peerDependenciesMeta": { + "@75lb/nature": { + "optional": true + } + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.2.tgz", + "integrity": "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==", + "dev": true + }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.3" + } + }, + "node_modules/foreground-child": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", + "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/form-data": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", + "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", + "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fromentries": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz", + "integrity": "sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/fs-exists-sync": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/fs-exists-sync/-/fs-exists-sync-0.1.0.tgz", + "integrity": "sha512-cR/vflFyPZtrN6b38ZyWxpWdhlXrzZEBawlpBQMq7033xVY7/kg0GDMBK5jg8lDYQckdJ5x/YC88lM3C7VMsLg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", + "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "functions-have-names": "^1.2.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-symbol-description": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", + "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-uri": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.3.tgz", + "integrity": "sha512-BzUrJBS9EcUb4cFol8r4W3v1cPsSyajLSthNkz5BxbpDcHN5tIrM10E2eNvfnvBn3DaT3DUgx0OpsBKkaOpanw==", + "dev": true, + "dependencies": { + "basic-ftp": "^5.0.2", + "data-uri-to-buffer": "^6.0.2", + "debug": "^4.3.4", + "fs-extra": "^11.2.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/get-uri/node_modules/fs-extra": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", + "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/global-modules": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-0.2.3.tgz", + "integrity": "sha512-JeXuCbvYzYXcwE6acL9V2bAOeSIGl4dD+iwLY9iUx2VBJJ80R18HCn+JCwHM9Oegdfya3lEkGCdaRkSyc10hDA==", + "dev": true, + "dependencies": { + "global-prefix": "^0.1.4", + "is-windows": "^0.2.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/global-prefix": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-0.1.5.tgz", + "integrity": "sha512-gOPiyxcD9dJGCEArAhF4Hd0BAqvAe/JzERP7tYumE4yIkmIedPUVXcJFWbV3/p/ovIIvKjkrTk+f1UVkq7vvbw==", + "dev": true, + "dependencies": { + "homedir-polyfill": "^1.0.0", + "ini": "^1.3.4", + "is-windows": "^0.2.0", + "which": "^1.2.12" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/global-prefix/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/globby": { + "version": "11.0.4", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.4.tgz", + "integrity": "sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.1.1", + "ignore": "^5.1.4", + "merge2": "^1.3.0", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/gopd": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.1.0.tgz", + "integrity": "sha512-FQoVQnqcdk4hVM4JN1eromaun4iuS34oStkdlLENLdpULsuQcTyXj8w7ayhuUfPwEYZ1ZOooOTT6fdA9Vmx/RA==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "node_modules/has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.1.0.tgz", + "integrity": "sha512-QLdzI9IIO1Jg7f9GT1gXpPpXArAn6cS31R1eEZqz08Gc+uQ8/XiqHWt17Fiw+2p6oTTIq5GXEpQkAlA88YRl/Q==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasha": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz", + "integrity": "sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==", + "dev": true, + "dependencies": { + "is-stream": "^2.0.0", + "type-fest": "^0.8.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/hasha/node_modules/type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/homedir-polyfill": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", + "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", + "dev": true, + "dependencies": { + "parse-passwd": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "node_modules/htmlparser2": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", + "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==", + "dev": true, + "dependencies": { + "domelementtype": "^1.3.1", + "domhandler": "^2.3.0", + "domutils": "^1.5.1", + "entities": "^1.1.1", + "inherits": "^2.0.1", + "readable-stream": "^3.1.1" + } + }, + "node_modules/http-assert": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/http-assert/-/http-assert-1.5.0.tgz", + "integrity": "sha512-uPpH7OKX4H25hBmU6G1jWNaqJGpTXxey+YOUizJUAgu0AjLUeC8D73hTrhvDS5D+GJN1DN1+hhc/eF/wpxtp0w==", + "dev": true, + "dependencies": { + "deep-equal": "~1.0.1", + "http-errors": "~1.8.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-errors": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", + "dev": true, + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/http-errors/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", + "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", + "dev": true, + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "dev": true, + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/inflation": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/inflation/-/inflation-2.1.0.tgz", + "integrity": "sha512-t54PPJHG1Pp7VQvxyVCJ9mBbjG3Hqryges9bXoOO6GExCPa+//i/d5GSuFtpx3ALLd7lgIAur6zrIlBQyJuMlQ==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + }, + "node_modules/internal-ip": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/internal-ip/-/internal-ip-6.2.0.tgz", + "integrity": "sha512-D8WGsR6yDt8uq7vDMu7mjcR+yRMm3dW8yufyChmszWRjcSHuxLBkR3GdS2HZAjodsaGuCvXeEJpueisXJULghg==", + "dev": true, + "dependencies": { + "default-gateway": "^6.0.0", + "ipaddr.js": "^1.9.1", + "is-ip": "^3.1.0", + "p-event": "^4.2.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/internal-ip?sponsor=1" + } + }, + "node_modules/internal-slot": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", + "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.0", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ip-address": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", + "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", + "dev": true, + "dependencies": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/ip-regex": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-4.3.0.tgz", + "integrity": "sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-arguments": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", + "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", + "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, + "node_modules/is-async-function": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.0.0.tgz", + "integrity": "sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "dev": true, + "dependencies": { + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-boolean-object": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.0.tgz", + "integrity": "sha512-kR5g0+dXf/+kXnqI+lu0URKYPKgICtHGGNCDSB10AaUFj3o/HkB3u7WfpRBJGFopxxY0oH3ux7ZsDjLtK7xqvw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", + "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", + "dev": true, + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.1.tgz", + "integrity": "sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==", + "dev": true, + "dependencies": { + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true, + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-finalizationregistry": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.0.tgz", + "integrity": "sha512-qfMdqbAQEwBw78ZyReKnlA8ezmPdb9BemzIIip/JkjaZUhitfXDkkr+3QTboW0JrSXT1QWyYShpvnNHGZ4c4yA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/is-generator-function": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", + "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-ip": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-ip/-/is-ip-3.1.0.tgz", + "integrity": "sha512-35vd5necO7IitFPjd/YBeqwWnyDWbuLH9ZXQdMfDA8TEo7pv5X8yfrvVO3xbJbLUlERCMvf6X0hTUamQxCYJ9Q==", + "dev": true, + "dependencies": { + "ip-regex": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", + "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", + "dev": true + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.0.tgz", + "integrity": "sha512-KVSZV0Dunv9DTPkhXwcZ3Q+tUc9TsaE1ZwX5J2WMvsSGS6Md8TFPun5uwh0yRdrNerI6vf/tbJxqSx4c1ZI1Lw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-path-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-3.0.0.tgz", + "integrity": "sha512-kyiNFFLU0Ampr6SDZitD/DwUo4Zs1nSdnygUBqsu3LooL00Qvb5j+UnvApUn/TTj1J3OuE6BTdQ5rudKmU2ZaA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-path-inside": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-4.0.0.tgz", + "integrity": "sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true + }, + "node_modules/is-regex": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.0.tgz", + "integrity": "sha512-B6ohK4ZmoftlUe+uvenXSbPJFo6U37BH7oO1B3nQH8f/7h27N56s85MhUtbFJAziz5dcmuR3i8ovUl35zp8pFA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "gopd": "^1.1.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", + "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-string": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.0.tgz", + "integrity": "sha512-PlfzajuF9vSo5wErv3MJAKD/nqf9ngAs1NFQYm16nUYFO2IzxJ2hcm+IOCg+EEopdykNNUhVq5cz35cAUxU8+g==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.0.tgz", + "integrity": "sha512-qS8KkNNXUZ/I+nX6QT8ZS1/Yx0A444yhzdTKxCzKkNjQ9sHErBxJnJAgh+f5YhusYECEcjo4XcyH87hn6+ks0A==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "has-symbols": "^1.0.3", + "safe-regex-test": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", + "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", + "dev": true, + "dependencies": { + "which-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", + "dev": true + }, + "node_modules/is-valid-element-name": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-valid-element-name/-/is-valid-element-name-1.0.0.tgz", + "integrity": "sha512-GZITEJY2LkSjQfaIPBha7eyZv+ge0PhBR7KITeCCWvy7VBQrCUdFkvpI+HrAPQjVtVjy1LvlEkqQTHckoszruw==", + "dev": true, + "dependencies": { + "is-potential-custom-element-name": "^1.0.0" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.3.tgz", + "integrity": "sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-windows": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-0.2.0.tgz", + "integrity": "sha512-n67eJYmXbniZB7RF4I/FTjK1s6RPOCTxhYrVYLRaCt3lF0mpWZPKr3T2LSZAqyjQsxR2qMmGYXXzK0YWwcPM1Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + }, + "node_modules/isbinaryfile": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-5.0.4.tgz", + "integrity": "sha512-YKBKVkKhty7s8rxddb40oOkuP0NbaeXrQvLin6QMHL7Ypiy2RW9LwOVrVgZRyOrhQlayMd9t+D8yDy8MKFTSDQ==", + "dev": true, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/gjtorikian/" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-hook": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz", + "integrity": "sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==", + "dev": true, + "dependencies": { + "append-transform": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "dev": true, + "dependencies": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-processinfo": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.3.tgz", + "integrity": "sha512-NkwHbo3E00oybX6NGJi6ar0B29vxyvNwoC7eJ4G4Yq28UfY758Hgn/heV8VRFhevPED4LXfFz0DQ8z/0kw9zMg==", + "dev": true, + "dependencies": { + "archy": "^1.0.0", + "cross-spawn": "^7.0.3", + "istanbul-lib-coverage": "^3.2.0", + "p-map": "^3.0.0", + "rimraf": "^3.0.0", + "uuid": "^8.3.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-processinfo/node_modules/p-map": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", + "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", + "dev": true, + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-processinfo/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", + "dev": true, + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", + "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", + "dev": true, + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/types": "^29.6.3", + "import-local": "^3.0.2", + "jest-cli": "^29.7.0" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", + "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", + "dev": true, + "dependencies": { + "execa": "^5.0.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", + "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^1.0.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0", + "pretty-format": "^29.7.0", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-circus/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true + }, + "node_modules/jest-circus/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-cli": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", + "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", + "dev": true, + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "create-jest": "^29.7.0", + "exit": "^0.1.2", + "import-local": "^3.0.2", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "yargs": "^17.3.1" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-config": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", + "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-jest": "^29.7.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-config/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-config/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-config/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true + }, + "node_modules/jest-config/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-diff/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-diff/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-diff/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true + }, + "node_modules/jest-docblock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", + "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", + "dev": true, + "dependencies": { + "detect-newline": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", + "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "jest-util": "^29.7.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-each/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true + }, + "node_modules/jest-environment-node": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", + "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/jest-junit": { + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/jest-junit/-/jest-junit-16.0.0.tgz", + "integrity": "sha512-A94mmw6NfJab4Fg/BlvVOUXzXgF0XIH6EmTgJ5NDPp4xoKq0Kr7sErb+4Xs9nZvu58pJojz5RFGpqnZYJTrRfQ==", + "dev": true, + "dependencies": { + "mkdirp": "^1.0.4", + "strip-ansi": "^6.0.1", + "uuid": "^8.3.2", + "xml": "^1.0.1" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/jest-junit/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/jest-leak-detector": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", + "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", + "dev": true, + "dependencies": { + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-leak-detector/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-leak-detector/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-leak-detector/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true + }, + "node_modules/jest-matcher-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-matcher-utils/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true + }, + "node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-message-util/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-message-util/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-message-util/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true + }, + "node_modules/jest-message-util/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-mock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-playwright-preset": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jest-playwright-preset/-/jest-playwright-preset-4.0.0.tgz", + "integrity": "sha512-+dGZ1X2KqtwXaabVjTGxy0a3VzYfvYsWaRcuO8vMhyclHSOpGSI1+5cmlqzzCwQ3+fv0EjkTc7I5aV9lo08dYw==", + "dev": true, + "dependencies": { + "expect-playwright": "^0.8.0", + "jest-process-manager": "^0.4.0", + "nyc": "^15.1.0", + "playwright-core": ">=1.2.0", + "rimraf": "^3.0.2", + "uuid": "^8.3.2" + }, + "peerDependencies": { + "jest": "^29.3.1", + "jest-circus": "^29.3.1", + "jest-environment-node": "^29.3.1", + "jest-runner": "^29.3.1" + } + }, + "node_modules/jest-playwright-preset/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-process-manager": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/jest-process-manager/-/jest-process-manager-0.4.0.tgz", + "integrity": "sha512-80Y6snDyb0p8GG83pDxGI/kQzwVTkCxc7ep5FPe/F6JYdvRDhwr6RzRmPSP7SEwuLhxo80lBS/NqOdUIbHIfhw==", + "dev": true, + "dependencies": { + "@types/wait-on": "^5.2.0", + "chalk": "^4.1.0", + "cwd": "^0.10.0", + "exit": "^0.1.2", + "find-process": "^1.4.4", + "prompts": "^2.4.1", + "signal-exit": "^3.0.3", + "spawnd": "^5.0.0", + "tree-kill": "^1.2.2", + "wait-on": "^7.0.0" + } + }, + "node_modules/jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", + "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", + "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", + "dev": true, + "dependencies": { + "jest-regex-util": "^29.6.3", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-runner": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", + "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", + "dev": true, + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/environment": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-leak-detector": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-resolve": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-util": "^29.7.0", + "jest-watcher": "^29.7.0", + "jest-worker": "^29.7.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/jest-runner/node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/jest-runtime": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", + "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/globals": "^29.7.0", + "@jest/source-map": "^29.6.3", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runtime/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-serializer-html": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/jest-serializer-html/-/jest-serializer-html-7.1.0.tgz", + "integrity": "sha512-xYL2qC7kmoYHJo8MYqJkzrl/Fdlx+fat4U1AqYg+kafqwcKPiMkOcjWHPKhueuNEgr+uemhGc+jqXYiwCyRyLA==", + "dev": true, + "dependencies": { + "diffable-html": "^4.1.0" + } + }, + "node_modules/jest-snapshot": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", + "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.7.0", + "semver": "^7.5.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-snapshot/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true + }, + "node_modules/jest-snapshot/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "leven": "^3.1.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-validate/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true + }, + "node_modules/jest-watch-typeahead": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/jest-watch-typeahead/-/jest-watch-typeahead-2.2.2.tgz", + "integrity": "sha512-+QgOFW4o5Xlgd6jGS5X37i08tuuXNW8X0CV9WNFi+3n8ExCIP+E1melYhvYLjv5fE6D0yyzk74vsSO8I6GqtvQ==", + "dev": true, + "dependencies": { + "ansi-escapes": "^6.0.0", + "chalk": "^5.2.0", + "jest-regex-util": "^29.0.0", + "jest-watcher": "^29.0.0", + "slash": "^5.0.0", + "string-length": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": "^14.17.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "jest": "^27.0.0 || ^28.0.0 || ^29.0.0" + } + }, + "node_modules/jest-watch-typeahead/node_modules/ansi-escapes": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-6.2.1.tgz", + "integrity": "sha512-4nJ3yixlEthEJ9Rk4vPcdBRkZvQZlYyu8j4/Mqz5sgIkddmEnH2Yj2ZrnP9S3tQOvSNRUIgVNF/1yPpRAGNRig==", + "dev": true, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watch-typeahead/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/jest-watch-typeahead/node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "dev": true, + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-watch-typeahead/node_modules/char-regex": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-2.0.2.tgz", + "integrity": "sha512-cbGOjAptfM2LVmWhwRFHEKTPkLwNddVmuqYZQt895yXwAsWsXObCG+YN4DGQ/JBtT4GP1a1lPPdio2z413LmTg==", + "dev": true, + "engines": { + "node": ">=12.20" + } + }, + "node_modules/jest-watch-typeahead/node_modules/string-length": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-5.0.1.tgz", + "integrity": "sha512-9Ep08KAMUn0OadnVaBuRdE2l615CQ508kr0XMadjClfYpdCyvrbFp6Taebo8yyxokQ4viUd/xPPUA4FGgUa0ow==", + "dev": true, + "dependencies": { + "char-regex": "^2.0.0", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watch-typeahead/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/jest-watcher": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", + "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", + "dev": true, + "dependencies": { + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.7.0", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "dev": true, + "dependencies": { + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/jju": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/jju/-/jju-1.4.0.tgz", + "integrity": "sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==", + "dev": true + }, + "node_modules/joi": { + "version": "17.13.3", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.3.tgz", + "integrity": "sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==", + "dev": true, + "dependencies": { + "@hapi/hoek": "^9.3.0", + "@hapi/topo": "^5.1.0", + "@sideway/address": "^4.1.5", + "@sideway/formula": "^3.0.1", + "@sideway/pinpoint": "^2.0.0" + } + }, + "node_modules/js-levenshtein-esm": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/js-levenshtein-esm/-/js-levenshtein-esm-1.2.0.tgz", + "integrity": "sha512-fzreKVq1eD7eGcQr7MtRpQH94f8gIfhdrc7yeih38xh684TNMK9v5aAu2wxfIRMk/GpAJRrzcirMAPIaSDaByQ==", + "dev": true + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsbn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", + "dev": true + }, + "node_modules/jsdoc-type-pratt-parser": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-4.1.0.tgz", + "integrity": "sha512-Hicd6JK5Njt2QB6XYFS7ok9e37O8AYk3jTcppG4YVQnYjOemymvTcmc7OWsmq/Qqj5TdRFO5/x/tIPmBeRtGHg==", + "dev": true, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/jsesc": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", + "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "node_modules/json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonc-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", + "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", + "dev": true + }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsonschema": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsonschema/-/jsonschema-1.4.1.tgz", + "integrity": "sha512-S6cATIPVv1z0IlxdN+zUk5EPjkGCdnhN4wVSBlvoUO1tOLJootbo9CquNJmbIh4yikWHiUedhRYrNPn1arpEmQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/junit-report-builder": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/junit-report-builder/-/junit-report-builder-5.1.1.tgz", + "integrity": "sha512-ZNOIIGMzqCGcHQEA2Q4rIQQ3Df6gSIfne+X9Rly9Bc2y55KxAZu8iGv+n2pP0bLf0XAOctJZgeloC54hWzCahQ==", + "dev": true, + "dependencies": { + "lodash": "^4.17.21", + "make-dir": "^3.1.0", + "xmlbuilder": "^15.1.1" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/junit-report-builder/node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/just-extend": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-6.2.0.tgz", + "integrity": "sha512-cYofQu2Xpom82S6qD778jBDpwvvy39s1l/hrYij2u9AMdQcGRpaBu6kY4mVhuno5kJVi1DAz4aiphA2WI1/OAw==", + "dev": true + }, + "node_modules/keygrip": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.1.0.tgz", + "integrity": "sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==", + "dev": true, + "dependencies": { + "tsscmp": "1.0.6" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/koa": { + "version": "2.15.3", + "resolved": "https://registry.npmjs.org/koa/-/koa-2.15.3.tgz", + "integrity": "sha512-j/8tY9j5t+GVMLeioLaxweJiKUayFhlGqNTzf2ZGwL0ZCQijd2RLHK0SLW5Tsko8YyyqCZC2cojIb0/s62qTAg==", + "dev": true, + "dependencies": { + "accepts": "^1.3.5", + "cache-content-type": "^1.0.0", + "content-disposition": "~0.5.2", + "content-type": "^1.0.4", + "cookies": "~0.9.0", + "debug": "^4.3.2", + "delegates": "^1.0.0", + "depd": "^2.0.0", + "destroy": "^1.0.4", + "encodeurl": "^1.0.2", + "escape-html": "^1.0.3", + "fresh": "~0.5.2", + "http-assert": "^1.3.0", + "http-errors": "^1.6.3", + "is-generator-function": "^1.0.7", + "koa-compose": "^4.1.0", + "koa-convert": "^2.0.0", + "on-finished": "^2.3.0", + "only": "~0.0.2", + "parseurl": "^1.3.2", + "statuses": "^1.5.0", + "type-is": "^1.6.16", + "vary": "^1.1.2" + }, + "engines": { + "node": "^4.8.4 || ^6.10.1 || ^7.10.1 || >= 8.1.4" + } + }, + "node_modules/koa-compose": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/koa-compose/-/koa-compose-4.1.0.tgz", + "integrity": "sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw==", + "dev": true + }, + "node_modules/koa-convert": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/koa-convert/-/koa-convert-2.0.0.tgz", + "integrity": "sha512-asOvN6bFlSnxewce2e/DK3p4tltyfC4VM7ZwuTuepI7dEQVcvpyFuBcEARu1+Hxg8DIwytce2n7jrZtRlPrARA==", + "dev": true, + "dependencies": { + "co": "^4.6.0", + "koa-compose": "^4.1.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/koa-etag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/koa-etag/-/koa-etag-4.0.0.tgz", + "integrity": "sha512-1cSdezCkBWlyuB9l6c/IFoe1ANCDdPBxkDkRiaIup40xpUub6U/wwRXoKBZw/O5BifX9OlqAjYnDyzM6+l+TAg==", + "dev": true, + "dependencies": { + "etag": "^1.8.1" + } + }, + "node_modules/koa-send": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/koa-send/-/koa-send-5.0.1.tgz", + "integrity": "sha512-tmcyQ/wXXuxpDxyNXv5yNNkdAMdFRqwtegBXUaowiQzUKqJehttS0x2j0eOZDQAyloAth5w6wwBImnFzkUz3pQ==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "http-errors": "^1.7.3", + "resolve-path": "^1.4.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/koa-static": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/koa-static/-/koa-static-5.0.0.tgz", + "integrity": "sha512-UqyYyH5YEXaJrf9S8E23GoJFQZXkBVJ9zYYMPGz919MSX1KuvAcycIuS0ci150HCoPf4XQVhQ84Qf8xRPWxFaQ==", + "dev": true, + "dependencies": { + "debug": "^3.1.0", + "koa-send": "^5.0.0" + }, + "engines": { + "node": ">= 7.6.0" + } + }, + "node_modules/koa-static/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/language-subtag-registry": { + "version": "0.3.23", + "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz", + "integrity": "sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==", + "dev": true + }, + "node_modules/language-tags": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.9.tgz", + "integrity": "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==", + "dev": true, + "dependencies": { + "language-subtag-registry": "^0.3.20" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lighthouse-logger": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-1.4.2.tgz", + "integrity": "sha512-gPWxznF6TKmUHrOQjlVo2UbaL2EJ71mb2CCeRs/2qBpi4L/g4LUVc9+3lKQ6DTUZwJswfM7ainGrLO1+fOqa2g==", + "dev": true, + "dependencies": { + "debug": "^2.6.9", + "marky": "^1.2.2" + } + }, + "node_modules/lighthouse-logger/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/lighthouse-logger/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "node_modules/lit": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/lit/-/lit-3.2.1.tgz", + "integrity": "sha512-1BBa1E/z0O9ye5fZprPtdqnc0BFzxIxTTOO/tQFmyC/hj1O3jL4TfmLBw0WEwjAokdLwpclkvGgDJwTIh0/22w==", + "dependencies": { + "@lit/reactive-element": "^2.0.4", + "lit-element": "^4.1.0", + "lit-html": "^3.2.0" + } + }, + "node_modules/lit-analyzer": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/lit-analyzer/-/lit-analyzer-2.0.3.tgz", + "integrity": "sha512-XiAjnwVipNrKav7r3CSEZpWt+mwYxrhPRVC7h8knDmn/HWTzzWJvPe+mwBcL2brn4xhItAMzZhFC8tzzqHKmiQ==", + "dev": true, + "dependencies": { + "@vscode/web-custom-data": "^0.4.2", + "chalk": "^2.4.2", + "didyoumean2": "4.1.0", + "fast-glob": "^3.2.11", + "parse5": "5.1.0", + "ts-simple-type": "~2.0.0-next.0", + "vscode-css-languageservice": "4.3.0", + "vscode-html-languageservice": "3.1.0", + "web-component-analyzer": "^2.0.0" + }, + "bin": { + "lit-analyzer": "cli.js" + } + }, + "node_modules/lit-analyzer/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/lit-analyzer/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/lit-analyzer/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/lit-analyzer/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/lit-analyzer/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/lit-analyzer/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/lit-analyzer/node_modules/parse5": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.0.tgz", + "integrity": "sha512-fxNG2sQjHvlVAYmzBZS9YlDp6PTSSDwa98vkD4QgVDDCAo84z5X1t5XyJQ62ImdLXx5NdIIfihey6xpum9/gRQ==", + "dev": true + }, + "node_modules/lit-analyzer/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/lit-element": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-4.1.1.tgz", + "integrity": "sha512-HO9Tkkh34QkTeUmEdNYhMT8hzLid7YlMlATSi1q4q17HE5d9mrrEHJ/o8O2D0cMi182zK1F3v7x0PWFjrhXFew==", + "dependencies": { + "@lit-labs/ssr-dom-shim": "^1.2.0", + "@lit/reactive-element": "^2.0.4", + "lit-html": "^3.2.0" + } + }, + "node_modules/lit-html": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-3.2.1.tgz", + "integrity": "sha512-qI/3lziaPMSKsrwlxH/xMgikhQ0EGOX2ICU73Bi/YHFvz2j/yMCIrw4+puF2IpQ4+upd3EWbvnHM9+PnJn48YA==", + "dependencies": { + "@types/trusted-types": "^2.0.2" + } + }, + "node_modules/load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/load-json-file/node_modules/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", + "dev": true, + "dependencies": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/load-json-file/node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", + "dev": true + }, + "node_modules/lodash.deburr": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/lodash.deburr/-/lodash.deburr-4.1.0.tgz", + "integrity": "sha512-m/M1U1f3ddMCs6Hq2tAsYThTBDaAKFDX3dwDo97GEYzamXi9SqUpjWi/Rrj/gf3X2n8ktwgZrlP1z6E3v/IExQ==", + "dev": true + }, + "node_modules/lodash.flattendeep": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", + "integrity": "sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==", + "dev": true + }, + "node_modules/lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", + "dev": true + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/log-update": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz", + "integrity": "sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==", + "dev": true, + "dependencies": { + "ansi-escapes": "^4.3.0", + "cli-cursor": "^3.1.0", + "slice-ansi": "^4.0.0", + "wrap-ansi": "^6.2.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/loupe": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.2.tgz", + "integrity": "sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg==", + "dev": true + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "dev": true, + "bin": { + "lz-string": "bin/bin.js" + } + }, + "node_modules/magic-string": { + "version": "0.30.14", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.14.tgz", + "integrity": "sha512-5c99P1WKTed11ZC0HMJOj6CDIue6F8ySu+bJL+85q1zBEIY8IklrJ1eiKC2NDRh3Ct3FcvmJPyQHb9erXMTJNw==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true, + "optional": true + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "dependencies": { + "tmpl": "1.0.5" + } + }, + "node_modules/map-or-similar": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/map-or-similar/-/map-or-similar-1.5.0.tgz", + "integrity": "sha512-0aF7ZmVon1igznGI4VS30yugpduQW3y3GkcgGJOp7d8x8QrizhigUxjI/m2UojsXXto+jLAH3KSz+xOJTiORjg==", + "dev": true + }, + "node_modules/marky": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/marky/-/marky-1.2.5.tgz", + "integrity": "sha512-q9JtQJKjpsVxCRVgQ+WapguSbKC3SQ5HEzFGPAJMStgh3QjCawp00UKv3MTTAArTmGmmPUvllHZoNbZ3gs0I+Q==", + "dev": true + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/memoizerific": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/memoizerific/-/memoizerific-1.11.3.tgz", + "integrity": "sha512-/EuHYwAPdLtXwAwSZkh/Gutery6pD2KYd44oQLhAvQp/50mpyduZh8Q7PYHXTCJ+wuXxt7oij2LXyIJOOYFPog==", + "dev": true, + "dependencies": { + "map-or-similar": "^1.5.0" + } + }, + "node_modules/memorystream": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", + "integrity": "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==", + "dev": true, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "dev": true + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/mustache": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz", + "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==", + "dev": true, + "bin": { + "mustache": "bin/mustache" + } + }, + "node_modules/nanocolors": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/nanocolors/-/nanocolors-0.2.13.tgz", + "integrity": "sha512-0n3mSAQLPpGLV9ORXT5+C/D4mwew7Ebws69Hx4E2sgz2ZA5+32Q80B9tL8PbL7XHnRDiAxH/pnrUJ9a4fkTNTA==", + "dev": true + }, + "node_modules/nanoid": { + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/netmask": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", + "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==", + "dev": true, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, + "node_modules/nise": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/nise/-/nise-6.1.1.tgz", + "integrity": "sha512-aMSAzLVY7LyeM60gvBS423nBmIPP+Wy7St7hsb+8/fc1HmeoHJfLO8CKse4u3BtOZvQLJghYPI2i/1WZrEj5/g==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^3.0.1", + "@sinonjs/fake-timers": "^13.0.1", + "@sinonjs/text-encoding": "^0.7.3", + "just-extend": "^6.2.0", + "path-to-regexp": "^8.1.0" + } + }, + "node_modules/nise/node_modules/@sinonjs/fake-timers": { + "version": "13.0.5", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-13.0.5.tgz", + "integrity": "sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^3.0.1" + } + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true + }, + "node_modules/node-preload": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", + "integrity": "sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==", + "dev": true, + "dependencies": { + "process-on-spawn": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/node-releases": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", + "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", + "dev": true + }, + "node_modules/normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "dependencies": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "node_modules/normalize-package-data/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-all": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/npm-run-all/-/npm-run-all-4.1.5.tgz", + "integrity": "sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "chalk": "^2.4.1", + "cross-spawn": "^6.0.5", + "memorystream": "^0.3.1", + "minimatch": "^3.0.4", + "pidtree": "^0.3.0", + "read-pkg": "^3.0.0", + "shell-quote": "^1.6.1", + "string.prototype.padend": "^3.0.0" + }, + "bin": { + "npm-run-all": "bin/npm-run-all/index.js", + "run-p": "bin/run-p/index.js", + "run-s": "bin/run-s/index.js" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/npm-run-all/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-all/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/npm-run-all/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-all/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/npm-run-all/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/npm-run-all/node_modules/cross-spawn": { + "version": "6.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.6.tgz", + "integrity": "sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==", + "dev": true, + "dependencies": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "engines": { + "node": ">=4.8" + } + }, + "node_modules/npm-run-all/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/npm-run-all/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-all/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/npm-run-all/node_modules/path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-all/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/npm-run-all/node_modules/shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "dev": true, + "dependencies": { + "shebang-regex": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-all/node_modules/shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-all/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-all/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/nyc/-/nyc-15.1.0.tgz", + "integrity": "sha512-jMW04n9SxKdKi1ZMGhvUTHBN0EICCRkHemEoE5jm6mTYcqcdas0ATzgUgejlQUHMvpnOZqGB5Xxsv9KxJW1j8A==", + "dev": true, + "dependencies": { + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "caching-transform": "^4.0.0", + "convert-source-map": "^1.7.0", + "decamelize": "^1.2.0", + "find-cache-dir": "^3.2.0", + "find-up": "^4.1.0", + "foreground-child": "^2.0.0", + "get-package-type": "^0.1.0", + "glob": "^7.1.6", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-hook": "^3.0.0", + "istanbul-lib-instrument": "^4.0.0", + "istanbul-lib-processinfo": "^2.0.2", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.0.2", + "make-dir": "^3.0.0", + "node-preload": "^0.2.1", + "p-map": "^3.0.0", + "process-on-spawn": "^1.0.0", + "resolve-from": "^5.0.0", + "rimraf": "^3.0.0", + "signal-exit": "^3.0.2", + "spawn-wrap": "^2.0.0", + "test-exclude": "^6.0.0", + "yargs": "^15.0.2" + }, + "bin": { + "nyc": "bin/nyc.js" + }, + "engines": { + "node": ">=8.9" + } + }, + "node_modules/nyc/node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/nyc/node_modules/cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "node_modules/nyc/node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true + }, + "node_modules/nyc/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/istanbul-lib-instrument": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", + "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", + "dev": true, + "dependencies": { + "@babel/core": "^7.7.5", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.0.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/nyc/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/nyc/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/p-map": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", + "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", + "dev": true, + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "dev": true + }, + "node_modules/nyc/node_modules/yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "dev": true, + "dependencies": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/object-inspect": { + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", + "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", + "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/only": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/only/-/only-0.0.2.tgz", + "integrity": "sha512-Fvw+Jemq5fjjyWz6CpKx6w9s7xxqo3+JCyM0WXWeCSOboZ8ABkyvP8ID4CZuChA/wxSx+XSJmdOm8rGVyJ1hdQ==", + "dev": true + }, + "node_modules/open": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", + "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", + "dev": true, + "dependencies": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/p-event": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/p-event/-/p-event-4.2.0.tgz", + "integrity": "sha512-KXatOjCRXXkSePPb1Nbi0p0m+gQAwdlbhi4wQKJPI1HsMQS9g+Sqp2o+QHziPr7eYJyOZet836KoHEVM1mwOrQ==", + "dev": true, + "dependencies": { + "p-timeout": "^3.1.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-map": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.2.tgz", + "integrity": "sha512-z4cYYMMdKHzw4O5UkWJImbZynVIo0lSGTXc7bzB1e/rrDqkgGUNysK/o4bTr+0+xKvvLoTyGqYC4Fgljy9qe1Q==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-timeout": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", + "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", + "dev": true, + "dependencies": { + "p-finally": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/pac-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.0.2.tgz", + "integrity": "sha512-BFi3vZnO9X5Qt6NRz7ZOaPja3ic0PhlsmCRYLOpN11+mWBCR6XJDqW5RF3j8jm4WGGQZtBA+bTfxYzeKW73eHg==", + "dev": true, + "dependencies": { + "@tootallnate/quickjs-emscripten": "^0.23.0", + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "get-uri": "^6.0.1", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.5", + "pac-resolver": "^7.0.1", + "socks-proxy-agent": "^8.0.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-resolver": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", + "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", + "dev": true, + "dependencies": { + "degenerator": "^5.0.0", + "netmask": "^2.0.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/package-hash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-4.0.0.tgz", + "integrity": "sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.15", + "hasha": "^5.0.0", + "lodash.flattendeep": "^4.4.0", + "release-zalgo": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse-passwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", + "integrity": "sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/parse5": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz", + "integrity": "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==", + "dev": true, + "dependencies": { + "entities": "^4.5.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", + "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", + "dev": true, + "dependencies": { + "parse5": "^6.0.1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter/node_modules/parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true + }, + "node_modules/parse5/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "dev": true + }, + "node_modules/path-equal": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/path-equal/-/path-equal-1.2.5.tgz", + "integrity": "sha512-i73IctDr3F2W+bsOWDyyVm/lqsXO47aY9nsFZUjTT/aljSbkxHxxCoyZ9UUrM8jK0JVod+An+rl48RCsvWM+9g==", + "dev": true, + "optional": true + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-to-regexp": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", + "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", + "dev": true, + "engines": { + "node": ">=16" + } + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/pathval": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", + "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==", + "dev": true, + "engines": { + "node": ">= 14.16" + } + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "dev": true + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pidtree": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.3.1.tgz", + "integrity": "sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA==", + "dev": true, + "bin": { + "pidtree": "bin/pidtree.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/pirates": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", + "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/playwright": { + "version": "1.49.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.49.0.tgz", + "integrity": "sha512-eKpmys0UFDnfNb3vfsf8Vx2LEOtflgRebl0Im2eQQnYMA4Aqd+Zw8bEOB+7ZKvN76901mRnqdsiOGKxzVTbi7A==", + "dev": true, + "dependencies": { + "playwright-core": "1.49.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.49.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.49.0.tgz", + "integrity": "sha512-R+3KKTQF3npy5GTiKH/T+kdhoJfJojjHESR1YEWhYuEKRVfVaxH3+4+GvXE5xyCngCxhxnykk0Vlah9v8fs3jA==", + "dev": true, + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/playwright/node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/polished": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/polished/-/polished-4.3.1.tgz", + "integrity": "sha512-OBatVyC/N7SCW/FaDHrSd+vn0o5cS855TOmYi4OkdWUMSJCET/xip//ch8xGUvtr3i44X9LVyWwQlRMTN3pwSA==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.17.8" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/portfinder": { + "version": "1.0.32", + "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.32.tgz", + "integrity": "sha512-on2ZJVVDXRADWE6jnQaX0ioEylzgBpQk8r55NE4wjXW1ZxO+BgDlY6DXwj20i0V8eB4SenDQ00WEaxfiIQPcxg==", + "dev": true, + "dependencies": { + "async": "^2.6.4", + "debug": "^3.2.7", + "mkdirp": "^0.5.6" + }, + "engines": { + "node": ">= 0.12.0" + } + }, + "node_modules/portfinder/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/portfinder/node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", + "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.4.49", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", + "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.1.tgz", + "integrity": "sha512-G+YdqtITVZmOJje6QkXQWzl3fSfMxFwm1tjTyo9exhkmWSqC4Yhd1+lug++IlR2mvRVAxEDDWYkQdeSztajqgg==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "dev": true, + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/process-on-spawn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.1.0.tgz", + "integrity": "sha512-JOnOPQ/8TZgjs1JIH/m9ni7FfimjNa/PRx7y/Wb5qdItsnhO0jE4AT7fC0HjC28DUQWDr50dwSYZLdRMlqDq3Q==", + "dev": true, + "dependencies": { + "fromentries": "^1.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/proxy-agent": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.4.0.tgz", + "integrity": "sha512-u0piLU+nCOHMgGjRbimiXmA9kM/L9EHh3zL81xCdp7m+Y2pHIsnmbdDoEDoAz5geaonNR6q6+yOPQs6n4T6sBQ==", + "dev": true, + "dependencies": { + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.1", + "https-proxy-agent": "^7.0.3", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.0.1", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-agent/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true + }, + "node_modules/pump": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", + "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==", + "dev": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/puppeteer-core": { + "version": "23.9.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-23.9.0.tgz", + "integrity": "sha512-hLVrav2HYMVdK0YILtfJwtnkBAwNOztUdR4aJ5YKDvgsbtagNr6urUJk9HyjRA9e+PaLI3jzJ0wM7A4jSZ7Qxw==", + "dev": true, + "dependencies": { + "@puppeteer/browsers": "2.4.1", + "chromium-bidi": "0.8.0", + "debug": "^4.3.7", + "devtools-protocol": "0.0.1367902", + "typed-query-selector": "^2.12.0", + "ws": "^8.18.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/puppeteer-core/node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "dev": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/pure-rand": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ] + }, + "node_modules/qs": { + "version": "6.13.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.1.tgz", + "integrity": "sha512-EJPeIn0CYrGu+hli1xilKAPXODtJ12T0sP63Ijx2/khC2JtuaN3JyNIpvmnkmaEtha9ocbG4A4cMcr+TvqvwQg==", + "dev": true, + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/queue-tick": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", + "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==", + "dev": true + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "dev": true, + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/raw-body/node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dev": true, + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/raw-body/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "dev": true, + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "dev": true, + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true + }, + "node_modules/read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==", + "dev": true, + "dependencies": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/read-pkg/node_modules/path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "dev": true, + "dependencies": { + "pify": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/recast": { + "version": "0.23.9", + "resolved": "https://registry.npmjs.org/recast/-/recast-0.23.9.tgz", + "integrity": "sha512-Hx/BGIbwj+Des3+xy5uAtAbdCyqK9y9wbBcDFDYanLS9JnMqf7OeF87HQwUimE87OEc72mr6tkKUKMBBL+hF9Q==", + "dev": true, + "dependencies": { + "ast-types": "^0.16.1", + "esprima": "~4.0.0", + "source-map": "~0.6.1", + "tiny-invariant": "^1.3.3", + "tslib": "^2.0.1" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/recast/node_modules/ast-types": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.16.1.tgz", + "integrity": "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==", + "dev": true, + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/recast/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "dependencies": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.7.tgz", + "integrity": "sha512-bMvFGIUKlc/eSfXNX+aZ+EL95/EgZzuwA0OBPTbZZDEJw/0AkentjMuM1oiRfwHrshqk4RzdgiTg5CcDalXN5g==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "which-builtin-type": "^1.1.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "dev": true + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.3.tgz", + "integrity": "sha512-vqlC04+RQoFalODCbCumG2xIOvapzVMHwsyIGM/SIE8fRhFFsXeH8/QQ+s0T0kDAhKc4k30s73/0ydkHQz6HlQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/release-zalgo": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", + "integrity": "sha512-gUAyHVHPPC5wdqX/LG4LWtRYtgjxyX78oanFNTMMyFEfOqdC54s3eE82imuWKbOeqYht2CrNf64Qb8vgmmtZGA==", + "dev": true, + "dependencies": { + "es6-error": "^4.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, + "node_modules/requireindex": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/requireindex/-/requireindex-1.2.0.tgz", + "integrity": "sha512-L9jEkOi3ASd9PYit2cwRfyppc9NoABujTP8/5gFcbERmo5jUoAKovIC3fsF17pkTnGsrByysqX+Kxd2OTNI1ww==", + "dev": true, + "engines": { + "node": ">=0.10.5" + } + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-cwd/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-dir": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-0.1.1.tgz", + "integrity": "sha512-QxMPqI6le2u0dCLyiGzgy92kjkkL6zO0XyvHzjdTNH3zM6e5Hz3BwG6+aEyNgiQ5Xz6PwTwgQEj3U50dByPKIA==", + "dev": true, + "dependencies": { + "expand-tilde": "^1.2.2", + "global-modules": "^0.2.3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-path": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/resolve-path/-/resolve-path-1.4.0.tgz", + "integrity": "sha512-i1xevIst/Qa+nA9olDxLWnLk8YZbi8R/7JPbCMcgyWaFR6bKWaexgJgEB5oc2PKMjYdrHynyz0NY+if+H98t1w==", + "dev": true, + "dependencies": { + "http-errors": "~1.6.2", + "path-is-absolute": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/resolve-path/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/resolve-path/node_modules/http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", + "dev": true, + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/resolve-path/node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", + "dev": true + }, + "node_modules/resolve-path/node_modules/setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "dev": true + }, + "node_modules/resolve.exports": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz", + "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rollup": { + "version": "4.28.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.28.0.tgz", + "integrity": "sha512-G9GOrmgWHBma4YfCcX8PjH0qhXSdH8B4HDE2o4/jaxj93S4DPCIDoLcXz99eWMji4hB29UFCEd7B2gwGJDR9cQ==", + "dev": true, + "dependencies": { + "@types/estree": "1.0.6" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.28.0", + "@rollup/rollup-android-arm64": "4.28.0", + "@rollup/rollup-darwin-arm64": "4.28.0", + "@rollup/rollup-darwin-x64": "4.28.0", + "@rollup/rollup-freebsd-arm64": "4.28.0", + "@rollup/rollup-freebsd-x64": "4.28.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.28.0", + "@rollup/rollup-linux-arm-musleabihf": "4.28.0", + "@rollup/rollup-linux-arm64-gnu": "4.28.0", + "@rollup/rollup-linux-arm64-musl": "4.28.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.28.0", + "@rollup/rollup-linux-riscv64-gnu": "4.28.0", + "@rollup/rollup-linux-s390x-gnu": "4.28.0", + "@rollup/rollup-linux-x64-gnu": "4.28.0", + "@rollup/rollup-linux-x64-musl": "4.28.0", + "@rollup/rollup-win32-arm64-msvc": "4.28.0", + "@rollup/rollup-win32-ia32-msvc": "4.28.0", + "@rollup/rollup-win32-x64-msvc": "4.28.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "dev": true, + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safe-array-concat": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz", + "integrity": "sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "get-intrinsic": "^1.2.4", + "has-symbols": "^1.0.3", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safe-regex-test": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", + "integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-regex": "^1.1.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "dev": true, + "optional": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "dev": true, + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "dev": true + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-quote": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.2.tgz", + "integrity": "sha512-AzqKpGKjrj7EM6rKVQEPpB288oCfnrEIuyoT9cyF4nmGa7V8Zk6f7RRqYisX8X9m+Q7bd632aZW4ky7EhbQztA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/sinon": { + "version": "19.0.2", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-19.0.2.tgz", + "integrity": "sha512-euuToqM+PjO4UgXeLETsfQiuoyPXlqFezr6YZDFwHR3t4qaX0fZUe1MfPMznTL5f8BWrVS89KduLdMUsxFCO6g==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^3.0.1", + "@sinonjs/fake-timers": "^13.0.2", + "@sinonjs/samsam": "^8.0.1", + "diff": "^7.0.0", + "nise": "^6.1.1", + "supports-color": "^7.2.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/sinon" + } + }, + "node_modules/sinon/node_modules/@sinonjs/fake-timers": { + "version": "13.0.5", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-13.0.5.tgz", + "integrity": "sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^3.0.1" + } + }, + "node_modules/sinon/node_modules/diff": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz", + "integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true + }, + "node_modules/slash": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", + "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", + "dev": true, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "dev": true, + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz", + "integrity": "sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==", + "dev": true, + "dependencies": { + "ip-address": "^9.0.5", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.4.tgz", + "integrity": "sha512-GNAq/eg8Udq2x0eNiFkr9gRg5bA7PXEWagQdeRX4cPSG+X/8V38v637gim9bjFptMk1QWsCTr0ttrJEiXbNnRw==", + "dev": true, + "dependencies": { + "agent-base": "^7.1.1", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/spawn-wrap": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-2.0.0.tgz", + "integrity": "sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==", + "dev": true, + "dependencies": { + "foreground-child": "^2.0.0", + "is-windows": "^1.0.2", + "make-dir": "^3.0.0", + "rimraf": "^3.0.0", + "signal-exit": "^3.0.2", + "which": "^2.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/spawn-wrap/node_modules/is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/spawn-wrap/node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/spawnd": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/spawnd/-/spawnd-5.0.0.tgz", + "integrity": "sha512-28+AJr82moMVWolQvlAIv3JcYDkjkFTEmfDc503wxrF5l2rQ3dFz6DpbXp3kD4zmgGGldfM4xM4v1sFj/ZaIOA==", + "dev": true, + "dependencies": { + "exit": "^0.1.2", + "signal-exit": "^3.0.3", + "tree-kill": "^1.2.2", + "wait-port": "^0.2.9" + } + }, + "node_modules/spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "dev": true, + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", + "dev": true + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.20", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.20.tgz", + "integrity": "sha512-jg25NiDV/1fLtSgEgyvVyDunvaNHbuwF9lfNV17gSmPFAlYzdfNBlLtLzXTevwkPj7DhGbmN9VnmJIgLnhvaBw==", + "dev": true + }, + "node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "dev": true + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/storybook": { + "version": "8.4.6", + "resolved": "https://registry.npmjs.org/storybook/-/storybook-8.4.6.tgz", + "integrity": "sha512-J6juZSZT2u3PUW0QZYZZYxBq6zU5O0OrkSgkMXGMg/QrS9to9IHmt4FjEMEyACRbXo8POcB/fSXa3VpGe7bv3g==", + "dev": true, + "dependencies": { + "@storybook/core": "8.4.6" + }, + "bin": { + "getstorybook": "bin/index.cjs", + "sb": "bin/index.cjs", + "storybook": "bin/index.cjs" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "prettier": "^2 || ^3" + }, + "peerDependenciesMeta": { + "prettier": { + "optional": true + } + } + }, + "node_modules/streamx": { + "version": "2.21.0", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.21.0.tgz", + "integrity": "sha512-Qz6MsDZXJ6ur9u+b+4xCG18TluU7PGlRfXVAAjNiGsFrBUt/ioyLkxbFaKJygoPs+/kW4VyBj0bSj89Qu0IGyg==", + "dev": true, + "dependencies": { + "fast-fifo": "^1.3.2", + "queue-tick": "^1.0.1", + "text-decoder": "^1.1.0" + }, + "optionalDependencies": { + "bare-events": "^2.2.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/string.prototype.padend": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.1.6.tgz", + "integrity": "sha512-XZpspuSB7vJWhvJc9DLSlrXl1mcA2BdoY5jjnS135ydXqLoqhs96JjDtCkjJEQHvfqZIp9hBuBMgI589peyx9Q==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz", + "integrity": "sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz", + "integrity": "sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/table-layout": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/table-layout/-/table-layout-4.1.1.tgz", + "integrity": "sha512-iK5/YhZxq5GO5z8wb0bY1317uDF3Zjpha0QFFLA8/trAoiLbQD0HUbMesEaxyzUgDxi2QlcbM8IvqOlEjgoXBA==", + "dev": true, + "dependencies": { + "array-back": "^6.2.2", + "wordwrapjs": "^5.1.0" + }, + "engines": { + "node": ">=12.17" + } + }, + "node_modules/tar-fs": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.6.tgz", + "integrity": "sha512-iokBDQQkUyeXhgPYaZxmczGPhnhXZ0CmrqI+MOb/WFGS9DW5wnfrLgtjUJBvz50vQ3qfRwJ62QVoCFu8mPVu5w==", + "dev": true, + "dependencies": { + "pump": "^3.0.0", + "tar-stream": "^3.1.5" + }, + "optionalDependencies": { + "bare-fs": "^2.1.1", + "bare-path": "^2.1.0" + } + }, + "node_modules/tar-stream": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "dev": true, + "dependencies": { + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/test-exclude/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/test-exclude/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/text-decoder": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.1.tgz", + "integrity": "sha512-x9v3H/lTKIJKQQe7RPQkLfKAnc9lUTkWDypIQgTzPJAq+5/GCDHonmshfvlsNSj58yyshbIJJDLmU15qNERrXQ==", + "dev": true + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "dev": true + }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "dev": true + }, + "node_modules/tinyglobby": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.10.tgz", + "integrity": "sha512-Zc+8eJlFMvgatPZTl6A9L/yht8QqdmUNtURHaKZLmKBE12hNPSrqNkUp2cs3M/UKmNVVAMFQYSjYIVHDjW5zew==", + "dev": true, + "dependencies": { + "fdir": "^6.4.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.2.tgz", + "integrity": "sha512-KnhMXsKSPZlAhp7+IjUkRZKPb4fUyccpDrdFXbi4QL1qkmFh9kVY09Yox+n4MaOb3lHZ1Tv829C3oaaXoMYPDQ==", + "dev": true, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/tinyrainbow": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz", + "integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==", + "dev": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz", + "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==", + "dev": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tr46": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.0.0.tgz", + "integrity": "sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==", + "dev": true, + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/ts-api-utils": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz", + "integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==", + "dev": true, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, + "node_modules/ts-dedent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.2.0.tgz", + "integrity": "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==", + "dev": true, + "engines": { + "node": ">=6.10" + } + }, + "node_modules/ts-lit-plugin": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/ts-lit-plugin/-/ts-lit-plugin-2.0.2.tgz", + "integrity": "sha512-DPXlVxhjWHxg8AyBLcfSYt2JXgpANV1ssxxwjY98o26gD8MzeiM68HFW9c2VeDd1CjoR3w7B/6/uKxwBQe+ioA==", + "dev": true, + "dependencies": { + "lit-analyzer": "^2.0.1", + "web-component-analyzer": "^2.0.0" + } + }, + "node_modules/ts-morph": { + "version": "24.0.0", + "resolved": "https://registry.npmjs.org/ts-morph/-/ts-morph-24.0.0.tgz", + "integrity": "sha512-2OAOg/Ob5yx9Et7ZX4CvTCc0UFoZHwLEJ+dpDPSUi5TgwwlTlX47w+iFRrEwzUZwYACjq83cgjS/Da50Ga37uw==", + "dev": true, + "dependencies": { + "@ts-morph/common": "~0.25.0", + "code-block-writer": "^13.0.3" + } + }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "optional": true, + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/ts-node/node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/ts-simple-type": { + "version": "2.0.0-next.0", + "resolved": "https://registry.npmjs.org/ts-simple-type/-/ts-simple-type-2.0.0-next.0.tgz", + "integrity": "sha512-A+hLX83gS+yH6DtzNAhzZbPfU+D9D8lHlTSd7GeoMRBjOt3GRylDqLTYbdmjA4biWvq2xSfpqfIDj2l0OA/BVg==", + "dev": true + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true + }, + "node_modules/tsscmp": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.6.tgz", + "integrity": "sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==", + "dev": true, + "engines": { + "node": ">=0.6.x" + } + }, + "node_modules/tunnel": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", + "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==", + "dev": true, + "engines": { + "node": ">=0.6.11 <=0.7.0 || >=0.7.3" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "dev": true, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz", + "integrity": "sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz", + "integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.3.tgz", + "integrity": "sha512-GsvTyUHTriq6o/bHcTd0vM7OQ9JEdlvluu9YISaA7+KzDzPaIzEeDFNkTfhdE3MYcNhNi0vq/LlegYgIs5yPAw==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13", + "reflect.getprototypeof": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", + "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-query-selector": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.0.tgz", + "integrity": "sha512-SbklCd1F0EiZOyPiW192rrHZzZ5sBijB6xM+cpmrwDqObvdtunOHHIk9fCGsoK5JVIYXoyEp4iEdE3upFH3PAg==", + "dev": true + }, + "node_modules/typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dev": true, + "dependencies": { + "is-typedarray": "^1.0.0" + } + }, + "node_modules/typescript": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz", + "integrity": "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.17.0.tgz", + "integrity": "sha512-409VXvFd/f1br1DCbuKNFqQpXICoTB+V51afcwG1pn1a3Cp92MqAUges3YjwEdQ0cMUoCIodjVDAYzyD8h3SYA==", + "dev": true, + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.17.0", + "@typescript-eslint/parser": "8.17.0", + "@typescript-eslint/utils": "8.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/typescript-json-schema": { + "version": "0.55.0", + "resolved": "https://registry.npmjs.org/typescript-json-schema/-/typescript-json-schema-0.55.0.tgz", + "integrity": "sha512-BXaivYecUdiXWWNiUqXgY6A9cMWerwmhtO+lQE7tDZGs7Mf38sORDeQZugfYOZOHPZ9ulsD+w0LWjFDOQoXcwg==", + "dev": true, + "optional": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "@types/node": "^16.9.2", + "glob": "^7.1.7", + "path-equal": "^1.1.2", + "safe-stable-stringify": "^2.2.0", + "ts-node": "^10.9.1", + "typescript": "~4.8.2", + "yargs": "^17.1.1" + }, + "bin": { + "typescript-json-schema": "bin/typescript-json-schema" + } + }, + "node_modules/typescript-json-schema/node_modules/@types/node": { + "version": "16.18.121", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.121.tgz", + "integrity": "sha512-Gk/pOy8H0cvX8qNrwzElYIECpcUn87w4EAEFXFvPJ8qsP9QR/YqukUORSy0zmyDyvdo149idPpy4W6iC5aSbQA==", + "dev": true, + "optional": true + }, + "node_modules/typescript-json-schema/node_modules/typescript": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz", + "integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==", + "dev": true, + "optional": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/typical": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/typical/-/typical-7.3.0.tgz", + "integrity": "sha512-ya4mg/30vm+DOWfBg4YK3j2WD6TWtRkCbasOJr40CseYENzCUby/7rIvXA99JGsQHeNxLbnXdyLLxKSv3tauFw==", + "dev": true, + "engines": { + "node": ">=12.17" + } + }, + "node_modules/unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/unbzip2-stream": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", + "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", + "dev": true, + "dependencies": { + "buffer": "^5.2.1", + "through": "^2.3.8" + } + }, + "node_modules/undici": { + "version": "5.28.4", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.4.tgz", + "integrity": "sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==", + "dev": true, + "dependencies": { + "@fastify/busboy": "^2.0.0" + }, + "engines": { + "node": ">=14.0" + } + }, + "node_modules/undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "dev": true + }, + "node_modules/unicorn-magic": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", + "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/unplugin": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-1.16.0.tgz", + "integrity": "sha512-5liCNPuJW8dqh3+DM6uNM2EI3MLLpCKp/KY+9pB5M2S2SR2qvvDHhKgBOaTWEbZTAws3CXfB0rKTIolWKL05VQ==", + "dev": true, + "dependencies": { + "acorn": "^8.14.0", + "webpack-virtual-modules": "^0.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", + "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/urlpattern-polyfill": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-10.0.0.tgz", + "integrity": "sha512-H/A06tKD7sS1O1X2SshBVeA5FLycRpjqiBeqGKmBwBDBy28EnRjORxTNe269KSSr5un5qyWi1iL61wLxpd+ZOg==", + "dev": true + }, + "node_modules/util": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", + "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "is-arguments": "^1.0.4", + "is-generator-function": "^1.0.7", + "is-typed-array": "^1.1.3", + "which-typed-array": "^1.1.2" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "dev": true, + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true, + "optional": true + }, + "node_modules/v8-to-istanbul": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vite": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.0.2.tgz", + "integrity": "sha512-XdQ+VsY2tJpBsKGs0wf3U/+azx8BBpYRHFAyKm5VeEZNOJZRB63q7Sc8Iup3k0TrN3KO6QgyzFf+opSbfY1y0g==", + "dev": true, + "dependencies": { + "esbuild": "^0.24.0", + "postcss": "^8.4.49", + "rollup": "^4.23.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vscode-css-languageservice": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/vscode-css-languageservice/-/vscode-css-languageservice-4.3.0.tgz", + "integrity": "sha512-BkQAMz4oVHjr0oOAz5PdeE72txlLQK7NIwzmclfr+b6fj6I8POwB+VoXvrZLTbWt9hWRgfvgiQRkh5JwrjPJ5A==", + "dev": true, + "dependencies": { + "vscode-languageserver-textdocument": "^1.0.1", + "vscode-languageserver-types": "3.16.0-next.2", + "vscode-nls": "^4.1.2", + "vscode-uri": "^2.1.2" + } + }, + "node_modules/vscode-html-languageservice": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/vscode-html-languageservice/-/vscode-html-languageservice-3.1.0.tgz", + "integrity": "sha512-QAyRHI98bbEIBCqTzZVA0VblGU40na0txggongw5ZgTj9UVsVk5XbLT16O9OTcbqBGSqn0oWmFDNjK/XGIDcqg==", + "dev": true, + "dependencies": { + "vscode-languageserver-textdocument": "^1.0.1", + "vscode-languageserver-types": "3.16.0-next.2", + "vscode-nls": "^4.1.2", + "vscode-uri": "^2.1.2" + } + }, + "node_modules/vscode-languageserver-textdocument": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.12.tgz", + "integrity": "sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==", + "dev": true + }, + "node_modules/vscode-languageserver-types": { + "version": "3.16.0-next.2", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.16.0-next.2.tgz", + "integrity": "sha512-QjXB7CKIfFzKbiCJC4OWC8xUncLsxo19FzGVp/ADFvvi87PlmBSCAtZI5xwGjF5qE0xkLf0jjKUn3DzmpDP52Q==", + "dev": true + }, + "node_modules/vscode-nls": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/vscode-nls/-/vscode-nls-4.1.2.tgz", + "integrity": "sha512-7bOHxPsfyuCqmP+hZXscLhiHwe7CSuFE4hyhbs22xPIhQ4jv99FcR4eBzfYYVLP356HNFpdvz63FFb/xw6T4Iw==", + "dev": true + }, + "node_modules/vscode-uri": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-2.1.2.tgz", + "integrity": "sha512-8TEXQxlldWAuIODdukIb+TR5s+9Ds40eSJrw+1iDDA9IFORPjMELarNQE3myz5XIkWWpdprmJjm1/SxMlWOC8A==", + "dev": true + }, + "node_modules/wait-on": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-7.2.0.tgz", + "integrity": "sha512-wCQcHkRazgjG5XoAq9jbTMLpNIjoSlZslrJ2+N9MxDsGEv1HnFoVjOCexL0ESva7Y9cu350j+DWADdk54s4AFQ==", + "dev": true, + "dependencies": { + "axios": "^1.6.1", + "joi": "^17.11.0", + "lodash": "^4.17.21", + "minimist": "^1.2.8", + "rxjs": "^7.8.1" + }, + "bin": { + "wait-on": "bin/wait-on" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/wait-port": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/wait-port/-/wait-port-0.2.14.tgz", + "integrity": "sha512-kIzjWcr6ykl7WFbZd0TMae8xovwqcqbx6FM9l+7agOgUByhzdjfzZBPK2CPufldTOMxbUivss//Sh9MFawmPRQ==", + "dev": true, + "dependencies": { + "chalk": "^2.4.2", + "commander": "^3.0.2", + "debug": "^4.1.1" + }, + "bin": { + "wait-port": "bin/wait-port.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wait-port/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/wait-port/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/wait-port/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/wait-port/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/wait-port/node_modules/commander": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/commander/-/commander-3.0.2.tgz", + "integrity": "sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow==", + "dev": true + }, + "node_modules/wait-port/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/wait-port/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/wait-port/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/web-component-analyzer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/web-component-analyzer/-/web-component-analyzer-2.0.0.tgz", + "integrity": "sha512-UEvwfpD+XQw99sLKiH5B1T4QwpwNyWJxp59cnlRwFfhUW6JsQpw5jMeMwi7580sNou8YL3kYoS7BWLm+yJ/jVQ==", + "dev": true, + "dependencies": { + "fast-glob": "^3.2.2", + "ts-simple-type": "2.0.0-next.0", + "typescript": "~5.2.0", + "yargs": "^17.7.2" + }, + "bin": { + "wca": "cli.js", + "web-component-analyzer": "cli.js" + } + }, + "node_modules/web-component-analyzer/node_modules/typescript": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", + "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/webpack-virtual-modules": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", + "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==", + "dev": true + }, + "node_modules/whatwg-url": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.0.0.tgz", + "integrity": "sha512-1lfMEm2IEr7RIV+f4lUNPOqfFL+pO+Xw3fJSqmjX9AbXcXcYOkCe1P6+9VBZB6n94af16NfZf+sSk0JCBZC9aw==", + "dev": true, + "dependencies": { + "tr46": "^5.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.0.tgz", + "integrity": "sha512-Ei7Miu/AXe2JJ4iNF5j/UphAgRoma4trE6PtisM09bPygb3egMH3YLW/befsWb1A1AxvNSFidOFTB18XtnIIng==", + "dev": true, + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.0", + "is-number-object": "^1.1.0", + "is-string": "^1.1.0", + "is-symbol": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.0.tgz", + "integrity": "sha512-I+qLGQ/vucCby4tf5HsLmGueEla4ZhwTBSqaooS+Y0BuxN4Cp+okmGuV+8mXZ84KDI9BA+oklo+RzKg0ONdSUA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.0.5", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.1.4", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.0.2", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.15" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dev": true, + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-module": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", + "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", + "dev": true + }, + "node_modules/which-typed-array": { + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.16.tgz", + "integrity": "sha512-g+N+GAWiRj66DngFwHvISJd+ITsyphZvD1vChfVg6cEdnzy53GzB3oy0fUNlvhz7H7+MiqhYr26qxQShCpKTTQ==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wordwrapjs": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wordwrapjs/-/wordwrapjs-5.1.0.tgz", + "integrity": "sha512-JNjcULU2e4KJwUNv6CHgI46UvDGitb6dGryHajXTDiLgg1/RiGoPSDw4kZfYnwGtEXf2ZMeIewDQgFGzkCB2Sg==", + "dev": true, + "engines": { + "node": ">=12.17" + } + }, + "node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/ws": { + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "dev": true, + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz", + "integrity": "sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==", + "dev": true + }, + "node_modules/xmlbuilder": { + "version": "15.1.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz", + "integrity": "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==", + "dev": true, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "dev": true, + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "node_modules/ylru": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ylru/-/ylru-1.4.0.tgz", + "integrity": "sha512-2OQsPNEmBCvXuFlIni/a+Rn+R2pHW9INm0BxXJ4hVDA8TirqMj+J/Rp9ItLatT/5pZqWwefVrTQcHpixsxnVlA==", + "dev": true, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "3.23.8", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", + "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..6567618 --- /dev/null +++ b/package.json @@ -0,0 +1,133 @@ +{ + "name": "@zebra-fed/zeta-web", + "version": "0.2.0", + "files": [ + "dist", + "custom-elements.json", + "assets" + ], + "main": "./dist/index.js", + "module": "./dist/index.js", + "exports": { + ".": "./dist/index.js", + "./assets/*": "./assets/*", + "./events.js": "./dist/events.js", + "./components/*": "./dist/components/*", + "./custom-elements.json": "./custom-elements.json", + "./index.css": "./dist/index.css" + }, + "description": "Zebra Zeta Design System - Web Components", + "repository": "https://github.com/zebratechnologies/zeta-web", + "author": "Zebra Front-end Development Team", + "license": "MIT", + "type": "module", + "types": "./dist/index.d.ts", + "declaration": "true", + "declarationMap": "true", + "customElements": "custom-elements.json", + "scripts": { + "analyze": "cem analyze", + "build-storybook:with-output-dir": "npm run build && storybook build -o ", + "build": "tsc -P tsconfig.build.json && npx clean-css-cli ./src/index.css -o ./dist/index.css", + "check": "npm-run-all analyze --parallel lint lint:lit-analyzer test docs", + "create": "cd scripts && node makeWebComponentTemplate.js", + "docs": "npx typedoc --logLevel Warn", + "gen:react-stories": "node ./scripts/react-stories.js", + "pregen:react": "npm run analyze", + "gen:react": "node scripts/make-react.js", + "lint:lit-analyzer": "lit-analyzer --quiet", + "lint": "eslint -f unix \"src/**/*.{ts,tsx}\"", + "localize:build": "lit-localize build", + "localize:extract": "lit-localize extract", + "prebuild": "npm run analyze && node scripts/make-index.js", + "prepack": "npm run build", + "prepare": "npm run build", + "prettier": "prettier src --write --ignore-unknown", + "start": "web-dev-server --node-resolve --open ./demo/ --watch", + "storybook:build": "npm run build && storybook build", + "storybook:test": "test-storybook", + "storybook": "cem analyze && storybook dev -p 6006", + "test:count": "tsx scripts/test/test_counter.ts", + "pretest:package": "npm pack --pack-destination ./test && cd test && npm install && cd ..", + "test:package": "cd test && npm run dev", + "test:watch": "web-test-runner \"src/test/**/*.test.ts\" --node-resolve --watch", + "test": "web-test-runner \"src/test/**/*.test.ts\" --node-resolve" + }, + "devDependencies": { + "@actions/core": "^1.11.1", + "@custom-elements-manifest/analyzer": "^0.10.3", + "@eslint/js": "^9.15.0", + "@etchteam/storybook-addon-status": "^5.0.0", + "@lit/localize-tools": "^0.8.0", + "@open-wc/lit-helpers": "^0.7.0", + "@open-wc/testing": "^4.0.0", + "@remcovaes/web-test-runner-vite-plugin": "^1.2.1", + "@storybook/addon-a11y": "^8.4.4", + "@storybook/addon-designs": "^8.0.3", + "@storybook/addon-docs": "^8.4.6", + "@storybook/addon-essentials": "^8.4.4", + "@storybook/addon-links": "^8.4.4", + "@storybook/blocks": "^8.4.4", + "@storybook/manager-api": "^8.4.6", + "@storybook/test": "^8.4.4", + "@storybook/test-runner": "^0.20.1", + "@storybook/theming": "^8.4.4", + "@storybook/web-components": "^8.4.4", + "@storybook/web-components-vite": "^8.4.4", + "@types/jest": "^29.5.14", + "@types/node": "^22.10.1", + "@typescript-eslint/eslint-plugin": "^8.17.0", + "@typescript-eslint/parser": "^8.17.0", + "@web/dev-server": "^0.4.6", + "@web/test-runner": "^0.19.0", + "@web/test-runner-commands": "^0.9.0", + "@web/test-runner-playwright": "^0.11.0", + "axe-playwright": "^2.0.2", + "cem-plugin-better-lit-types": "^0.2.1", + "cem-plugin-custom-jsdoc-tags": "^1.1.2", + "command-line-args": "^6.0.1", + "del": "^8.0.0", + "esbuild": "0.24.0", + "eslint": "^9.15.0", + "eslint-config-prettier": "^9.1.0", + "eslint-formatter-unix": "^8.40.0", + "eslint-plugin-lit": "^1.15.0", + "eslint-plugin-lit-a11y": "^4.1.4", + "eslint-plugin-storybook": "^0.11.1", + "eslint-plugin-tsdoc": "^0.4.0", + "eslint-plugin-wc": "^2.2.0", + "lit-analyzer": "^2.0.3", + "npm-run-all": "^4.1.5", + "prettier": "3.4.1", + "react": "^18.3.1", + "sinon": "^19.0.2", + "storybook": "^8.4.4", + "ts-lit-plugin": "^2.0.2", + "ts-morph": "^24.0.0", + "typescript": "^5.7.2", + "typescript-eslint": "^8.16.0", + "vite": "^6.0.2" + }, + "dependencies": { + "@fontsource/ibm-plex-sans": "^5.0.21", + "@lit/localize": "^0.12.2", + "@zebra-fed/zeta-icons": "^0.9.0", + "lit": "^3.2.1" + }, + "resolutions": { + "react-router-dom": "^6.3.0", + "react-router": "^6.3.0" + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + } +} diff --git a/release-please-config.json b/release-please-config.json new file mode 100644 index 0000000..ab27310 --- /dev/null +++ b/release-please-config.json @@ -0,0 +1,65 @@ +{ + "packages": { + ".": { + "release-type": "node" + } + }, + "extra-files": [ + ".storybook/preview-head.html" + ], + "changelog-sections": [ + { + "type": "feat", + "section": "✨ New Features" + }, + { + "type": "fix", + "section": "🪲 Bug Fixes" + }, + { + "type": "revert", + "section": "👀 Reverts" + }, + { + "type": "docs", + "section": "📈 Documentation" + }, + { + "type": "deps", + "section": "⛓️ Dependencies" + }, + { + "type": "test", + "section": "🧪 Tests" + }, + { + "type": "chore", + "section": "🧹 Miscellaneous Chores" + }, + { + "type": "perf", + "section": "Performance Improvements", + "hidden": true + }, + { + "type": "refactor", + "section": "Code Refactoring", + "hidden": true + }, + { + "type": "style", + "section": "Styles", + "hidden": true + }, + { + "type": "build", + "section": "Build System", + "hidden": true + }, + { + "type": "ci", + "section": "Continuous Integration", + "hidden": true + } + ] +} \ No newline at end of file diff --git a/scripts/assets/react.template b/scripts/assets/react.template new file mode 100644 index 0000000..84312b0 --- /dev/null +++ b/scripts/assets/react.template @@ -0,0 +1,10 @@ +import * as React from "react"; +import { createComponent } from "@lit/react"; +import type { Zeta_replacecap_ as Zeta_replacecap_WC } from "zeta-web"; + +export const Zeta_replacecap_ = createComponent({ + tagName: "zeta-_replacelower_", + elementClass: Zeta_replacecap_WC, + react: React, + events: {} +}); \ No newline at end of file diff --git a/scripts/assets/web.stories.template b/scripts/assets/web.stories.template new file mode 100644 index 0000000..1c0585d --- /dev/null +++ b/scripts/assets/web.stories.template @@ -0,0 +1,13 @@ +import type { Meta, StoryObj } from "@storybook/web-components"; +import type { Zeta_replacecap_ } from "./_replacelower_.js"; +import "./_replacelower_.js"; + +const meta: Meta<Zeta_replacecap_> = { + component: "zeta-_replacelower_", + args: {}, + argTypes: {} +}; +export default meta; + +export const _replacecap_: StoryObj<Zeta_replacecap_> = {}; + diff --git a/scripts/assets/web.styles.template b/scripts/assets/web.styles.template new file mode 100644 index 0000000..e69de29 diff --git a/scripts/assets/web.template b/scripts/assets/web.template new file mode 100644 index 0000000..d870474 --- /dev/null +++ b/scripts/assets/web.template @@ -0,0 +1,32 @@ +import { html } from "lit"; +import { customElement } from "lit/decorators.js"; +import { ContourableElement } from "../../mixins/contour.js"; +import styles from "./_replacelower_.styles.js"; + +/** Zeta_replacecap_ web component. + * + * //TODO: Add description + * //TODO: Add slot description + * //TODO: Add figma link(s) + * //TODO: Add storybook link + * + * @public */ +@customElement("zeta-_replacelower_") +export class Zeta_replacecap_ extends ContourableElement { + + + protected override render() { + return html` + <div>// TODO:</div> + `; + } + + static styles = [super.styles ?? [], styles]; +} + +declare global { + interface HTMLElementTagNameMap { + "zeta-_replacelower_": Zeta_replacecap_; + } +} + diff --git a/scripts/assets/web.test.template b/scripts/assets/web.test.template new file mode 100644 index 0000000..a60453d --- /dev/null +++ b/scripts/assets/web.test.template @@ -0,0 +1,19 @@ +import { fixture, html, unsafeStatic, expect } from "@open-wc/testing"; +import type { Zeta_replacecap_ } from "../../src/index.js"; +import "../../src/index.js"; + +describe("zeta-_replacelower_", () => { + let subject: Zeta_replacecap_; + + const createComponent = (template = `<zeta-_replacelower_></zeta-_replacelower_>`) => { + return fixture<Zeta_replacecap_>(html`${unsafeStatic(template)}`); + }; + + beforeEach(async () => { + subject = await createComponent(); + }); + + it("meets accessibility requirements", async () => { + await expect(subject).shadowDom.to.be.accessible(); + }); +}); diff --git a/scripts/esbuild-lit-css.js b/scripts/esbuild-lit-css.js new file mode 100644 index 0000000..b6a2783 --- /dev/null +++ b/scripts/esbuild-lit-css.js @@ -0,0 +1,38 @@ +import fs from "fs/promises"; +import path from "path"; + +const regex = /(([\r\n]|.)*)/; + +export default () => { + const suffix = "?inline"; + + return { + name: "esbuild-plugin-litify-inline-css", + setup(build) { + build.onResolve({ filter: /.*\.css\?inline$/ }, async (args) => { + return { + path: path.resolve(args.resolveDir, args.path.replace(suffix, "")), + suffix: "?inline", + }; + }); + build.onLoad({ filter: /.*\.css$/ }, async (args) => { + // console.log("🥇 LitCss ", args.path, args.suffix); + let fileData = await fs.readFile(args.path, "utf-8"); + if (args.suffix === suffix) { + // console.log("✅ LitCss", args.path); + return { + contents: fileData.replace( + regex, + (m) => `import { css } from "lit"; export default css\`${m}\`;` + ), + loader: "js", + }; + } + return { + contents: fileData, + loader: "css", + }; + }); + }, + }; +}; diff --git a/scripts/make-index.js b/scripts/make-index.js new file mode 100644 index 0000000..254d8d8 --- /dev/null +++ b/scripts/make-index.js @@ -0,0 +1,22 @@ +import fs from "fs"; +import path from "path"; +import { getAllComponents } from "./shared.js"; +const components = getAllComponents() + //Remove "BaseXxx" Classes, these dont need exporting. + .filter(({ name }) => !name.startsWith("Base")) + .sort((a, b) => a.name.localeCompare(b.name)); + +const sourceDir = path.join("./src"); + +let imports = components.map( + ({ name, path }) => + `import { ${name} } from "${path.replace(/\.ts/, ".js").replace(/^src\//, "./")}";` +); +imports.unshift(`import "./index.css";`); +let exports = `\nexport {\n${components.map(({ name }) => ` ${name}`).join(",\n")}\n};`; + +fs.writeFileSync( + path.join(sourceDir, "index.ts"), + `${imports.join("\n")}${exports}`, + "utf8" +); diff --git a/scripts/make-react.js b/scripts/make-react.js new file mode 100644 index 0000000..3a2e798 --- /dev/null +++ b/scripts/make-react.js @@ -0,0 +1,168 @@ +/* LICENSE +Copyright (c) 2020 A Beautiful Site, LLC +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. +File source: https://github.com/shoelace-style/shoelace/blob/next/scripts/make-react.js +Modifications: made to fit zeta-web +*/ +import commandLineArgs from "command-line-args"; +import { promises as fs } from "fs"; +import path from "path"; +import { deleteAsync } from "del"; +import prettier from "prettier"; +import { getAllComponents } from "./shared.js"; +import { pascalCaseFromKebab } from "./utils.js"; + +const { outdir } = commandLineArgs({ name: "outdir", type: String }); + +const reactSrcDir = path.join("../zeta-react/src"); +const reactStorybookDir = path.join(reactSrcDir, "../.storybook"); +const webStorybookDir = path.join("./.storybook"); +//TODO change this + +const errors = {}; +const _collateError = (type, file) => { + if (errors[type] === undefined) { + errors[type] = []; + } + errors[type].push(file); +}; +const _reportErrors = () => { + const errArr = Object.entries(errors); + errArr.forEach(([type, files]) => { + console.error(`⛔ ${files.length} ${type} error(s) found:`); + files.forEach((file) => console.error(` - ${file}`)); + }); + process.exitCode = errArr.length > 0 ? 1 : 0; +}; + +// Fetch component metadata +const components = getAllComponents(true).map((component) => { + const tagWithoutPrefix = component.tagName.replace(/^zeta-/, ""); + const componentDir = path.join(reactSrcDir, tagWithoutPrefix); + const componentFile = path.join(componentDir, "index.ts"); + return { ...component, tagWithoutPrefix, componentDir, componentFile }; +}); + +const index = components.map( + ({ name, tagWithoutPrefix }) => + `export { default as ${name} } from './${tagWithoutPrefix}/index.js';` +); + +const copyStorybookFiles = () => { + const storybookPaths = ["./ZetaTheme.js", "./manager.js"]; + return storybookPaths.map((filePath) => { + return fs.copyFile( + path.join(webStorybookDir, filePath), + path.join(reactStorybookDir, filePath) + ); + }); +}; + +const generateComponentFile = ({ + tagName, + name, + events, + jsDoc, + componentDir, + componentFile /*, path*/, +}) => { + let hasErrored = false; + // const importPath = path.replace(/\.ts$/, '.js'); + const enhancedEvents = (events || []).map(({ name: fullName }) => { + const [_, eventClass, name] = /(?:([a-zA-Z0-9_]+):)?([a-zA-Z-]+)/g.exec( + fullName + ); + const pascalName = pascalCaseFromKebab(name); + const eventName = eventClass || `${pascalName}Event`; + const reactName = `on${pascalName}`; + return { + imprt: `import type { ${eventName}Detail } from '@zebra-fed/zeta-web/events.js';`, + exprt: `export type { ${eventName}Detail } from '@zebra-fed/zeta-web/events.js';`, + reactName, + name, + eventName, + }; + }); + const eventDefs = {}; + enhancedEvents.forEach(({ reactName, name, eventName }) => { + try { + const key = `${reactName}: '${name}'`; + if (eventDefs[key] === undefined) { + console.log("New event found: " + key, eventName, eventDefs); + if (name === "undefined") { + throw new Error(`Event name is undefined: ${tagName}`); + } + eventDefs[key] = `${eventName}Detail`; + } else { + eventDefs[key] = `${eventDefs[key]} | ${eventName}Detail`; + console.log("Defined event: " + key, eventName, eventDefs); + } + } catch (e) { + _collateError("undefinedEvent", componentFile); + // console.warn(e.message); + hasErrored = true; + } + }); + if (hasErrored) { + // console.error("⛔ Error in generating event details", componentFile); + return; + } + + const eventNameImport = + (events || []).length > 0 + ? `import { type EventName } from '@lit/react';` + : ``; + + return Promise.all([ + prettier.format( + ` + import * as React from 'react'; + import { createComponent } from '@lit/react'; + import { ${name} } from '@zebra-fed/zeta-web'; + + ${eventNameImport} + ${[...new Set(enhancedEvents.map(({ imprt }) => imprt))].join("\r\n")} + ${[...new Set(enhancedEvents.map(({ exprt }) => exprt))].join("\r\n")} + + ${jsDoc || ""} + const reactWrapper = createComponent({ + tagName: '${tagName}', + elementClass: ${name}, + react: React, + events: { + ${Object.entries(eventDefs) + .map(([key, value]) => `${key} as EventName<CustomEvent<${value}>>`) + .join(",\r\n")} + }, + displayName: "${name}" + }) + + export default reactWrapper + `, + { + parser: "babel-ts", + } + ), + fs.mkdir(componentDir, { recursive: true }), + ]).then(([source, _fileName]) => fs.writeFile(componentFile, source, "utf8")); +}; + +const main = async () => { + // Clear build directory + await deleteAsync(reactSrcDir, { force: true }).then(() => + fs.mkdir(reactSrcDir, { recursive: true }) + ); + + Promise.all([ + ...components.map(generateComponentFile), + // Generate the index file + fs.writeFile(path.join(reactSrcDir, "index.ts"), index.join("\n"), "utf8"), + ...copyStorybookFiles(), + ]); + + return _reportErrors(errors); +}; + +main(); diff --git a/scripts/makeWebComponentTemplate.js b/scripts/makeWebComponentTemplate.js new file mode 100644 index 0000000..fa798f6 --- /dev/null +++ b/scripts/makeWebComponentTemplate.js @@ -0,0 +1,68 @@ +import { write, log, replaceAllInTemplate, capitalize, insert } from "./utils.js"; +import { readFileSync, existsSync, writeFileSync } from "fs"; +import path from "path"; + +const buildFile = (nameLower, nameCap, outDir, template, type) => { + if (existsSync(path.normalize(outDir))) { + log("⚔️ --- " + nameCap + " " + type + " already exists"); + } else { + let populatedTemplate = template; + populatedTemplate = replaceAllInTemplate("_replacelower_", nameLower, populatedTemplate); + populatedTemplate = replaceAllInTemplate("_replacecap_", nameCap, populatedTemplate); + write(outDir, populatedTemplate); + log(" --- Zeta" + nameCap + " " + type + " created\n"); + } +}; + +const appendIndex = outDir => { + const indexPath = "../src/index.ts"; + const indexFile = readFileSync(indexPath); + if (indexFile) { + let indexFileStr = String(indexFile); + + const importStatement = 'import "' + outDir + '.ts";' + "\n"; + const importStatementLocation = indexFileStr.indexOf("export") - 2; + + indexFileStr = insert(indexFileStr, importStatement, importStatementLocation); + + const exportStatement = 'export * from "' + outDir + '.js";'; + + indexFileStr += exportStatement; + + writeFileSync(indexPath, indexFileStr); + } +}; + +const main = () => { + const nameArr = process.argv.slice(2).filter(x => x); + let nameLower, + dirName = null; + nameLower = nameArr[0].toLowerCase(); + if (nameArr.length == 1) { + // dir and file same name + dirName = nameLower; + } else if (nameArr.length == 2) { + // dir and file differnet names + dirName = nameArr[1]; + } else { + log("❎ --- No name entered"); + } + + if (dirName) { + const outDir = "../src/components/" + dirName + "/" + nameLower; + const testOutDir = "../test/" + dirName + "/" + nameLower; + const nameCap = nameLower + .split("-") + .map(x => capitalize(x)) + .join(""); + + buildFile(nameLower, nameCap, outDir + ".ts", readFileSync(path.normalize("./assets/web.template")), "Component"); + buildFile(nameLower, nameCap, outDir + ".css", readFileSync(path.normalize("./assets/web.styles.template")), "Style"); + buildFile(nameLower, nameCap, outDir + ".stories.ts", readFileSync(path.normalize("./assets/web.stories.template")), "Story"); + buildFile(nameLower, nameCap, testOutDir + ".test.ts", readFileSync(path.normalize("./assets/web.test.template")), "Test"); + + appendIndex(outDir); + } +}; + +main(); diff --git a/scripts/react-stories.js b/scripts/react-stories.js new file mode 100644 index 0000000..9ecf28d --- /dev/null +++ b/scripts/react-stories.js @@ -0,0 +1,431 @@ +import { readdirSync, lstatSync, readFileSync } from "fs"; +import * as path from "path"; +import { + write, + log, + replaceAllInTemplate, + capitalize, + convertPathToUnixStyle, + pascalCaseFromKebab, +} from "./utils.js"; +const ZETA_REACT_NAME = "zeta-react"; +const SRC_FOLDER_PATH = `${ZETA_REACT_NAME}/src`; +const SRC_FOLDER = `${SRC_FOLDER_PATH}/`; +const SRC_PATH = `../${SRC_FOLDER}`; +const ZETA_FOLDER_PATH = `../${ZETA_REACT_NAME}/`; + +/** Slots that don't work in React. + * + * TODO: Add more to this list. */ +const slotsToChange = ["svg"]; + +/** Directories that should be ignored when parsing files for react conversion. + * + * TODO: Add more to this list. */ +const foldersToIgnore = ["mixins"]; + +/** RegExp to find only alphabetical characters. */ +const azOnlyRegExp = /[^a-z]/g; + +/** Recursively navigates directory returning all children + * + * @param {string} dir Initial directory to search. + * @param {string[]} files List of files found so far. + * + * @returns {string[]} List of all files found so far. + */ +const walker = (dir, files = []) => { + const dirFiles = readdirSync(path.normalize(dir)); + for (const f of dirFiles) { + const stat = lstatSync(path.normalize(dir + path.sep + f)); + if (stat.isDirectory()) { + walker(dir + path.sep + f, files); + } else { + files.push(convertPathToUnixStyle(dir + path.sep + f)); + } + } + return files; +}; + +/** Gets all web component stories. + * + * @returns {string[]} List of all web component file paths. + */ +const getWebStories = () => + walker("./src").filter((x) => x.match(/\.stories\.(ts|js|tsx|jsx)$/)); + +/** Gets the name of a component from its path. + * + * @param {string} path path of component file. + * @returns {string} Name of component. + */ +const pathToName = (path) => + convertPathToUnixStyle(path).split("/").pop().split(".").shift(); + +/** For a given tag, find the closing tag in tags list. + * + * @param {string[]} tags List of all tags from code block. + * @param {number} index index of opening tag in tags list. + * @param {string} type Type of the tag - i.e. svg. + * + * @modifies This function has the side-effect of changing the very array it is looping over, tags. + */ +const findAndReplaceClosingSlotTag = (tags, index, type) => { + /** Counts duplicate internal types in tags list. */ + let counter = 0; + + /** When closing tag is found, this will be its place in the array */ + let found = 0; + + /** Index iterated as we know current tag is not self-closing. */ + index = index + 1; + + /** Iterate over all tags to find closing tag that matches. */ + for (index; index < tags.length; index++) { + /** Finds type of tag. */ + const type2 = tags[index].split(" ")[0].slice(1).replace(azOnlyRegExp, ""); + /**Check if type2 is what we are looking for, and if it is a closing tag, but not self-closing.. */ + if ( + type2 == type && + tags[index][1] != "/" && + tags[index].trim().endsWith("/>") + ) { + /** If this is an opening tag of the same type, then we know it is entirely within the parent type, iterate count to ignore this later on.*/ + counter += 1; + } else if (type2 == type) { + /** Check if we are waiting for any internal elements to be closed. */ + if (counter == 0) { + /** Found closing tag (hopefully). Assign this value to `found` */ + found = index; + break; + } else { + /** Reduce count by 1 as we have closed an internal element. */ + counter += -1; + } + } + } + + /** Check if the closing tag was found. */ + if (found != 0) { + /** Add the closing div after the found tag, to close the new wrapping div added above. This is added in directly in tags array, so is a side-effect. */ + tags[found] = tags[found] + "</div>"; + } else { + //TODO: This is an error state. We should do something here. + } +}; + +/** Function that refactors web-component slot elements for consumption in react storybook. + * + * @param {string[]} tags All the HTML tags being parsed. (This could be altered as a side-effect of this function). + * @param {string} tag Current tag from `tag` array. + * @param {number} index Index of the current `tag` within `tags`. + * @param {string} type Type of tag (i.e. svg). + * + * @modifies This function has the side-effect of changing the very array it is looping over, tags. + * + * @returns {string} Refactored tag ready for consumption in react story. + */ +const parseWebStoryTagForSlot = (tags, tag, index, type) => { + /** Value in slot i.e: name if 'slot="name" */ + const slotValue = tag.split("slot")[1].split('"')[1]; + + /** Tag string split on word slot */ + const splitOnSlot = tag.split("slot"); + + /** Tag string with slot removed. */ + const unslotted = splitOnSlot[0] + splitOnSlot[1].slice(slotValue.length + 3); + + /** Add div with slot and append to unslotted tag string. */ + tag = `<div slot=\"${slotValue}\">${unslotted}`; + + /** Find self-closing tag. */ + if (tag.trim().endsWith("/>")) { + /** If tag is self closing, then we can just close the div after it. */ + tag = tag + "</div>"; + } else { + /** Otherwise, this function finds and replaces the closing tag further on in the array.*/ + findAndReplaceClosingSlotTag(tags, index, type); + } + return tag; +}; + +/** Parses and refactors HTML tags from web-component + * + * @param {string[]} tags List of HTML tags from web-component story, split by '<'. + * + * @returns {string[]} Refactored tags for react story. + */ +const parseWebStory = (tags) => + tags.map((tag, index) => { + const type = tag.split(" ")[0].slice(1); + return tag.includes("slot") && slotsToChange.includes(type) + ? parseWebStoryTagForSlot(tags, tag, index, type) + : tag; + }); + +/** Refactors web-component story render method to work with react. + * + * @param {string} story Content of web-component story. + * @param {string} name Name of the stories component. + * + * @returns {string} refactored (reactified) story. + */ +const buildReactifiedStoryRender = (story, name) => { + console.log("buildReactifiedStoryRender", story, name); + /** Initial part of string is <ZetaX ..., this remains unchanged. */ + const a = story.trim().slice(0, capitalize(name).length + 5) + " {...args} >"; + + /** Split the remaining string on each '<' to find all the HTML tags. */ + let c1 = story + .slice(story.indexOf(">")) + .replace("`", "") + .slice(1) + .split("<") + .map((x, i) => (i == 0 ? x : `<${x}`)); + + /** Final tag of string is </ZetaX >, this remains unchanged. */ + const c = c1.pop(); + + /** All intermediate tags should be parsed for slots and potentially refactored. */ + const b = parseWebStory(c1).join(""); + + /** Rebuild story string in refactored form. */ + return a + b + c; +}; + +/** Creates a storybook file for react component based off web component storybook. + * + * @param {string} path Path of existing storybook for web component. + */ +const outputStoryFile = (storyPath) => { + const outputPath = `${storyPath.replace("./src/", ZETA_FOLDER_PATH)}x`; + + /** kebab-case name of story.*/ + const name = pathToName(storyPath); + const camelCaseName = capitalize(name); + + /** All text of story. */ + const storyText = readFileSync(storyPath).toString(); + + let replaceImport = ""; + + if (storyText.includes("render")) { + // log(`🔨 --- Render method found. Update this to use react props: ${path.resolve(outputPath)}`) + replaceImport = 'import React from "react";'; + } + + /** Reads in file, removes lit import and adds render. */ + let storyWeb = storyText + .split(/$/) + .map((x) => + x.includes("render") + ? "//TODO: This render method may need to change for react\n" + x + : x + ) + .join("\n"); + + /** Builds populated template. */ + let populatedTemplate = storyWeb; + populatedTemplate = replaceAllInTemplate( + 'import { html } from "lit";', + replaceImport, + populatedTemplate + ); + populatedTemplate = replaceAllInTemplate( + "web-components", + "react", + populatedTemplate + ); + populatedTemplate = replaceAllInTemplate( + "Meta<Zeta", + "Meta<typeof Zeta", + populatedTemplate + ); + populatedTemplate = replaceAllInTemplate( + "StoryObj<Zeta", + "StoryObj<typeof Zeta", + populatedTemplate + ); + populatedTemplate = replaceAllInTemplate( + `"zeta-${name}"`, + `Zeta${camelCaseName}`, + populatedTemplate + ); + populatedTemplate = replaceAllInTemplate( + `"Zeta${camelCaseName}"`, + `Zeta${camelCaseName}`, + populatedTemplate + ); + populatedTemplate = replaceAllInTemplate( + `import type { Zeta${camelCaseName} } from`, + `import Zeta${camelCaseName} from`, + populatedTemplate + ); + populatedTemplate = replaceAllInTemplate( + `import { ifDefined } from "lit/directives/if-defined.js";`, + "", + populatedTemplate + ); + populatedTemplate = replaceAllInTemplate( + `import { spread } from "@open-wc/lit-helpers";`, + "", + populatedTemplate + ); + + populatedTemplate = populatedTemplate.replace( + /import \{ Zeta([A-Z][a-zA-Z]+) \} from "..\//, + 'import Zeta$1 from "../', + populatedTemplate + ); + populatedTemplate = populatedTemplate.replace( + /from "((?:..\/)+)(?:.+\/)?([a-zA-Z-]+).js"/g, + `from "\$1src/\$2/index.js"`, + populatedTemplate + ); //Path of react index file + populatedTemplate = populatedTemplate.replace( + /import "((?:..\/)+)(?:.+\/)?([a-zA-Z-]+).js"/g, + (_, a, b) => + `import Zeta${pascalCaseFromKebab(b)} from "${a}src/${b}/index.js"`, + populatedTemplate + ); //Path of other react components used in this story index file + populatedTemplate = populatedTemplate.replace( + /ifDefined\(([a-zA-Z\._-]+)\)/g, + "$1", + populatedTemplate + ); //removes ifDefined + populatedTemplate = populatedTemplate.replace( + /\$\{spread\(args\)\}/g, + "{...args}", + populatedTemplate + ); //converts the usage of spread() function to actual ...spread in React + populatedTemplate = populatedTemplate.replace( + /\$\{args\.([A-Za-z0-9._-]+)\}/g, + "{args.$1}", + populatedTemplate + ); //converts ${...} to {...} + populatedTemplate = populatedTemplate.replace( + /\$\{/g, + "{", + populatedTemplate + ); //converts remaining ${ to { . Probably dont need the line above + + /** Refactor local imports to come from react index file */ + let localImports = []; + const populatedTemplateArr = populatedTemplate + .split(/$/) + .map((line) => { + if (foldersToIgnore.some((element) => line.includes(element))) { + localImports.push(line.split("{")[1].split("}")[0]); + return null; + } + return line; + }) + .filter((item) => item); + + //TODO: This doesnt remove props from template + //TODO: When in template this does not wrap slot children + populatedTemplate = populatedTemplateArr.join("\n"); + /** Refactor lit html in render methods */ + populatedTemplate = populatedTemplate + .split(/html`\s*/s) + .map((y, index) => { + // if(name === 'button') console.log("htmlToReactRender", name, index); + /** Filters out any parts of the story that are irrelevant */ + if (y.includes("</Zeta")) { + return buildReactifiedStoryRender(y, name).replace("`", ""); + } + // if (index != 0) { + + // y = '<>' + y + // return y.replace('`', '</>'); + // } + //Change web component element with React Element + return y + .replace( + /<(\/?)zeta-([a-z0-9-]+)(\s|>|\/>)/g, + (match, group1, group2, group3, endTick) => { + return `<${group1}Zeta${capitalize(group2)}${group3}`; + } + ) + .replace(/(\s+)[\.@\?]([a-zA-Z0-9_-]+)=/g, "$1$2=") + .replace(/<\/([a-zA-Z-]+)\s*>\s*`/gs, "</$1>"); + }) + .join(""); + + let x = populatedTemplate + .split("content:") + .map((x, i) => (i == 0 ? x : "content:" + x)); + let a = x.shift(); + x = x.map((b, i7) => { + const c = b.split("<").map((x, i) => (i == 0 ? x : "<" + x)); + c.forEach((d, index) => { + if (d.startsWith("<zeta-")) { + c[index] = "<Zeta" + d[6].toUpperCase() + d.slice(7); + } else if (d.startsWith("</zeta-")) { + c[index] = "</Zeta" + d[7].toUpperCase() + d.slice(8); + } + if (d.includes("slot=")) { + let a = c[index].split("slot=")[0]; + let b = c[index].split("slot=")[1].split('"').slice(2); + let name1 = c[index].split('slot="')[1].split('"')[0]; + c[index] = '<div slot="' + name1 + '">' + a + b; + let name = c[index].slice(1).split(" ").shift(); + let i = index + 1; + let counter = 0; + + for (i; i < c.length; i++) { + if (c[i].startsWith("<" + name)) { + counter++; + } else if (c[i].startsWith("</" + name)) { + if (counter > 0) { + counter--; + } else { + c[i] = c[i] + "</div>"; + break; + } + } + } + } + }); + return c.join(""); + }); + + populatedTemplate = a + x; + + /** Write out story file. */ + write(outputPath, populatedTemplate); +}; + +const main = () => { + /// Build react storybooks + log("\n📖 --- Looking for un-reactified storybook files"); + const allWebStories = getWebStories(); + const currentReactStoriesConv = walker(SRC_PATH) + .filter((x) => x.includes(".stories.")) + .map((x) => x.replace("/zeta-react/", "/").slice(0, -1)); + const allStoriesNeeded = allWebStories.filter( + (x) => !currentReactStoriesConv.includes(x) + ); + + if (allStoriesNeeded.length > 0) { + if (allStoriesNeeded.length == 1) { + log("😮 --- 1 un-reactified story found"); + } else { + log("😮 --- 2 un-reactified stories found"); + } + allStoriesNeeded.forEach((file) => outputStoryFile(file)); + log( + `✅ --- All ${allStoriesNeeded.length} stories un-reactified to ${path.resolve(ZETA_FOLDER_PATH)}` + ); + } else { + log("🚀 --- No un-reactified stories found"); + } + log("✅ --- Done\n"); +}; + +main(); + +//TODO: + +// Dont do mdx files or styles or mixins +// Fix import lines diff --git a/scripts/shared.js b/scripts/shared.js new file mode 100644 index 0000000..1e37d16 --- /dev/null +++ b/scripts/shared.js @@ -0,0 +1,30 @@ +/* LICENSE +Copyright (c) 2020 A Beautiful Site, LLC +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. +File source: https://github.com/shoelace-style/shoelace/blob/next/scripts/shared.js +*/ +import fs from 'fs'; +/** Gets an array of components from a CEM object. */ +export function getAllComponents(onlyRegisteredComponents) { + const metadata = JSON.parse(fs.readFileSync('./custom-elements.json'), 'utf8'); + const allComponents = []; + + metadata.modules.map(module => { + module.declarations?.map(declaration => { + if (declaration.customElement) { + const component = declaration; + const path = module.path; + + if (component) { + allComponents.push(Object.assign(component, { path })); + } + } + }); + }); + // console.log(`ℹ️ All Components: #:${allComponents.length}. -tagName:${allComponents.filter((a) => !!a.tagName).length}`); + // console.log(`ℹ️ Components without tagname: ${allComponents.filter((a) => !a.tagName).reduce((acc, {name}) => {return `${acc}${name}, `}, '')}`); + if(onlyRegisteredComponents === true) return allComponents.filter((a) => !!a.tagName); + return allComponents; + } \ No newline at end of file diff --git a/scripts/test/output/test_counts.md b/scripts/test/output/test_counts.md new file mode 100644 index 0000000..0e1b958 --- /dev/null +++ b/scripts/test/output/test_counts.md @@ -0,0 +1,48 @@ +| Component | Accessibility | Content | Dimensions | Styling | Interaction | Golden | Performance | Unorganised | Total Tests | +| -------------------------- | ------------- | ------- | ---------- | ------- | ----------- | ------ | ----------- | ----------- | ----------- | +| Action Menu Button | 1 | 0 | 3 | 0 | 2 | 0 | 0 | 0 | 6 | +| Appbar | 0 | 6 | 0 | 0 | 0 | 0 | 0 | 0 | 6 | +| Avatar | 0 | 8 | 0 | 0 | 0 | 0 | 0 | 0 | 8 | +| Bottom Sheet | 3 | 9 | 6 | 0 | 0 | 0 | 0 | 0 | 18 | +| Breadcrumb | 1 | 2 | 0 | 0 | 1 | 0 | 0 | 0 | 4 | +| Button | 1 | 1 | 0 | 0 | 3 | 0 | 0 | 0 | 5 | +| Icon Button | 1 | 1 | 0 | 9 | 3 | 0 | 0 | 0 | 14 | +| Card Header | 1 | 4 | 0 | 0 | 0 | 0 | 0 | 0 | 5 | +| Checkbox | 3 | 5 | 0 | 0 | 2 | 0 | 0 | 0 | 10 | +| Assist Chip | 1 | 2 | 0 | 0 | 0 | 0 | 0 | 0 | 3 | +| Filter Chip | 1 | 2 | 0 | 0 | 0 | 0 | 0 | 0 | 3 | +| Input Chip | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 2 | +| Dialog | 0 | 5 | 0 | 0 | 0 | 0 | 0 | 0 | 5 | +| Dropdown Menu Button | 1 | 1 | 2 | 0 | 6 | 1 | 0 | 0 | 11 | +| Dropdown Menu Item | 1 | 4 | 0 | 6 | 1 | 0 | 0 | 0 | 12 | +| Droppable | 1 | 4 | 0 | 2 | 0 | 0 | 0 | 0 | 7 | +| Fab | 1 | 5 | 2 | 4 | 0 | 0 | 0 | 0 | 12 | +| File Upload | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | +| Upload Item | 0 | 4 | 0 | 0 | 1 | 0 | 0 | 0 | 5 | +| Global Header | 1 | 0 | 0 | 2 | 0 | 0 | 0 | 0 | 3 | +| Grid Menu Item | 1 | 3 | 0 | 0 | 0 | 0 | 0 | 0 | 4 | +| In Page Banner | 1 | 2 | 0 | 0 | 0 | 0 | 0 | 0 | 3 | +| Navigation Bar | 1 | 4 | 1 | 0 | 0 | 0 | 0 | 0 | 6 | +| Navigation Drawer | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | +| Navigation Drawer Footer | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | +| Navigation Drawer Item | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | +| Navigation Drawer Sub Item | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | +| Navigation Header | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | +| Navigation Profile | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | +| Navigation Rail Item | 0 | 3 | 0 | 0 | 1 | 0 | 0 | 0 | 4 | +| Navigation Rail | 0 | 2 | 0 | 0 | 0 | 0 | 0 | 0 | 2 | +| Pagination | 0 | 8 | 0 | 0 | 2 | 0 | 0 | 0 | 10 | +| Progress Bar | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | +| Progress Circle | 0 | 6 | 0 | 0 | 1 | 0 | 0 | 0 | 7 | +| Radio Button | 3 | 4 | 0 | 0 | 2 | 0 | 0 | 0 | 9 | +| Search | 0 | 6 | 3 | 1 | 3 | 0 | 0 | 0 | 13 | +| Segmented Control | 0 | 3 | 0 | 0 | 1 | 0 | 0 | 0 | 4 | +| Slider | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | +| Slider Input Field | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | +| Snackbar | 1 | 3 | 0 | 9 | 6 | 0 | 0 | 0 | 19 | +| Stepper Input | 0 | 9 | 0 | 0 | 0 | 0 | 0 | 0 | 9 | +| Stepper | 0 | 3 | 0 | 0 | 0 | 0 | 0 | 0 | 3 | +| Switch | 0 | 4 | 0 | 10 | 1 | 0 | 0 | 0 | 15 | +| Tag | 1 | 4 | 0 | 0 | 0 | 0 | 0 | 0 | 5 | +| Text Input | 0 | 18 | 0 | 3 | 1 | 0 | 0 | 0 | 22 | +| Total Tests | 37 | 146 | 17 | 46 | 37 | 1 | 0 | 0 | 284 | diff --git a/scripts/test/test_counter.ts b/scripts/test/test_counter.ts new file mode 100644 index 0000000..07efbb3 --- /dev/null +++ b/scripts/test/test_counter.ts @@ -0,0 +1,355 @@ +import { Project, SyntaxKind, Node, SourceFile } from "ts-morph"; + +import * as fs from "fs"; +import * as path from "path"; + +import { + ASTStructure, + TestCounts, + TestFiles, + writeToFile, + writeJsonToFile, + isDescribeExpression, + isItExpression, +} from "./test_counter_utils"; + +/** + * Resolves the given output path to an absolute path and ensures that the directory exists. + * If the directory does not exist, it creates the directory recursively. + * + * @param outputPath - The path to the output directory. + * @returns The absolute path to the output directory. + */ +function getOutputDir(outputPath: string): string { + const outputDir = path.resolve(outputPath).replace(/^(\.\.(\/|\\|$))+/, ""); + if (!fs.existsSync(outputDir)) { + fs.mkdirSync(outputDir, { recursive: true }); + } + return outputDir; +} + +/** + * Recursively retrieves all test file paths from a given directory. + * A test file is identified by its `.test.ts` extension. + * + * @returns An array of strings, each representing the path to a test file. + */ +function getTestFiles(): string[] { + const inputDir = path.resolve("src/test").replace(/^(\.\.(\/|\\|$))+/, ""); + + let testFilePaths: string[] = []; + + function readDirectory(directory: string) { + const entities = fs.readdirSync(directory, { withFileTypes: true }); + for (const entity of entities) { + const fullPath = path.join(directory, entity.name); + if (entity.isDirectory()) { + readDirectory(fullPath); + } else if (entity.isFile() && entity.name.endsWith(".test.ts")) { + testFilePaths.push(fullPath); + } + } + } + + readDirectory(inputDir); + return testFilePaths; +} + +/** + * Parses the given test files and returns a map of their formatted AST structures. + * + * @param paths - An array of file paths to the test files that need to be parsed. + * @returns A map where the keys are the file names and the values are the formatted AST structures. + */ +function parseTestFiles(paths: string[]): TestFiles { + var parsedTestFiles: TestFiles = new Map(); + const project = new Project(); + for (const path of paths) { + const sourceFile: SourceFile = project.addSourceFileAtPath(path); + + // get the AST structure of the source file + const astTree: ASTStructure = nodeToASTStructure(sourceFile); + + // extract describe and it statements from the AST structure + const extractedStatements: ASTStructure[] = extractStatements( + astTree.children![0] + ); + + // format the extracted statements into a nested record object + const formattedRecord: Record<string, any> = + formatExtractedStatements(extractedStatements); + + // add the formatted tree to the map at the file name + parsedTestFiles[path.split("\\").pop()!] = formattedRecord; + } + return parsedTestFiles; +} + +/** + * Recursively converts a Node object into a JSON-like structure. + * + * @param node - The Node object to be converted. + * @returns An ASTStructure object representing the node and its children. + */ +function nodeToASTStructure(node: Node): ASTStructure { + // recursively get children of node + const children = node.getChildren().map(nodeToASTStructure); + return { + kind: SyntaxKind[node.getKind()], + text: node.getText() as String, + children: children.length > 0 ? children : undefined, + }; +} + +/** + * Extracts `describe` and `it` statements from an AST structure. + * + * This function traverses an Abstract Syntax Tree (AST) and collects all + * `describe` and `it` statements into a hierarchical structure. The `describe` + * statements can contain nested `describe` and `it` statements as children. + * + * @param node - The root node of the AST structure to traverse. + * @returns An array of `describe` statements, each containing nested `describe` + * and `it` statements as children. + */ +function extractStatements(node: ASTStructure): ASTStructure[] { + const describeStatements: ASTStructure[] = []; + + function traverse(node: ASTStructure, parentDescribe?: ASTStructure) { + if (isDescribeExpression(node)) { + const newDescribe = { kind: node.kind, text: node.text, children: [] }; + + // if there is a parent describe, add the new describe as a child + if (parentDescribe) { + parentDescribe.children!.push(newDescribe); + } else { + // otherwise, add the new describe to the top level + describeStatements.push(newDescribe); + } + parentDescribe = newDescribe; + } else if (isItExpression(node)) { + const newIt = { kind: node.kind, text: node.text }; + // if there is a parent describe, add the new it as a child + if (parentDescribe) { + parentDescribe.children!.push(newIt); + } + } + + // recursively traverse children + if (node.children) { + node.children.forEach((child) => traverse(child, parentDescribe)); + } + } + + traverse(node); + return describeStatements; +} + +/** + * Formats an ASTStructure into a nested record object. + * + * @param tree - An array of ASTStructure nodes representing the tree to format. + * @returns A nested record object where each `describe` statement creates a new nested object + * and each `it` statement adds to a `tests` array within the appropriate nested object. + * + * The function processes nodes with the following rules: + * - Nodes with `kind` "ExpressionStatement" and text starting with "describe" create new nested objects. + * - Nodes with `kind` "ExpressionStatement" and text starting with "it" add to a `tests` array within the appropriate nested object. + * - Nested `describe` statements are handled recursively to maintain the hierarchy. + * + * Example: + * Given an AST structure representing: + * + * describe('suite1', () => { + * it('test1', () => {}); + * describe('suite2', () => { + * it('test2', () => {}); + * }); + * }); + * + * The function will return: + * { + * suite1: { + * tests: ['test1'], + * suite2: { + * tests: ['test2'] + * } + * } + * } + */ +function formatExtractedStatements(tree: ASTStructure[]): Record<string, any> { + const result: Record<string, any> = {}; + + function addToResult(node: ASTStructure, parentKey?: string) { + if (isDescribeExpression(node)) { + const describeText: string = node.text.matchDescribeText(); + // if the describe statement is nested + if (parentKey) { + if (!result[parentKey] && !result[parentKey.split(".").shift()!]) { + result[parentKey] = {}; + } + if (!result[parentKey.split(".").shift()!]) { + result[parentKey][describeText] = {}; + } + node.children?.forEach((child) => + addToResult(child, `${parentKey}.${describeText}`) + ); + } else { + // else the describe statement is at the root level + result[describeText] = {}; + node.children?.forEach((child) => addToResult(child, describeText)); + } + } else if (isItExpression(node) && parentKey) { + const itText = node.text.matchItText(); + const keys = parentKey.split("."); + let current = result; + keys.forEach((key) => { + if (!current[key]) { + current[key] = {}; + } + current = current[key]; + }); + if (!current["tests"]) { + current["tests"] = []; + } + current["tests"].push(itText); + } + } + + tree.forEach((node) => addToResult(node)); + return result; +} + +/** + * Counts the number of tests in each test file and categorizes them. + * + * @param {TestFiles} testFiles - An object where the key is the file name and the value is a map of component names to their test groups. + * @returns {TestCounts} A map where the key is the formatted file name and the value is a map of test categories to their counts. + * + * The function iterates over each test file and its components, counting the number of tests in each category. + * Tests that are not in category groups are counted under the "unorganised" group. + */ +function countTests(testFiles: TestFiles): TestCounts { + var testCounts: TestCounts = new Map(); + // loop through each top level object + for (const [fileName, file] of Object.entries(testFiles)) { + const fileTestCounts: Map<string, number> = new Map(); + + // loop through each component + for (const [componentName, testGroups] of Object.entries( + file as Map<String, Map<String, String[]>> + )) { + const testGroupsArray = testGroups as Array<string>; + + // if the component has tests that aren't in a test group + if (testGroupsArray["tests"]) { + const testsLength = testGroupsArray["tests"].length; + if (!fileTestCounts["unorganised"]) { + fileTestCounts["unorganised"] = testsLength; + } else { + fileTestCounts["unorganised"] += testsLength; + } + } + + // loop through each test group + for (const [groupName, tests] of Object.entries(testGroups)) { + const sanitizedGroupName = groupName.sanitizeGroupName(); + const testsArray = tests as Array<string>; + + // if the test group is in the testCategories array + if ( + testCategories.includes(sanitizedGroupName) || + (testCategories.includes(groupName) && testsArray["tests"]) + ) { + const testsLength = testsArray["tests"].length; + // if the test group has not been looped through yet + if (!fileTestCounts[sanitizedGroupName]) { + fileTestCounts[sanitizedGroupName] = testsLength; + // if the test group has been looped through + } else { + fileTestCounts[sanitizedGroupName] += testsLength; + } + // if the test group is not in the testCategories array + } else if (testsArray["tests"]) { + const testsLength = testsArray["tests"].length; + + if (!fileTestCounts["unorganised"]) { + fileTestCounts["unorganised"] = testsLength; + } else { + fileTestCounts["unorganised"] += testsLength; + } + } + } + } + + testCounts[fileName.sanitizeFileName()] = fileTestCounts; + } + return testCounts; +} + +/** + * Generates a Markdown table summarizing test counts for various test categories. + * + * @param testCounts - An object containing the counts of tests for each component. + * @returns A string representing the Markdown table. + * + */ +function generateMDTable(testCounts: TestCounts) { + var data = [ + `| Component | ${testCategories.join(" | ").sanitizeGroupName()} | Unorganised | Total Tests |`, + "| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |", + ].addComponentandTotalRows(testCounts, testCategories); + + return data.join("\n"); +} + +/** + * This is the source of truth for the test categories. + * If you change the categories here, you must also change them in the test files. + * You can add or remove categories as needed. + * Tests that don't belong to one of these test groups well be organised into the "Unorganised" group. + * The order of the categories here will be reflected in the output. + * You can have the word 'Tests' in there or not. It wouldn't matter. + */ +const testCategories: string[] = [ + "Accessibility", + "Content", + "Dimensions", + "Styling", + "Interaction", + "Golden", + "Performance", +]; + +async function main() { + // get output directory + const outputDir = path + .resolve("scripts/test/output") + .replace(/^(\.\.(\/|\\|$))+/, ""); + if (!fs.existsSync(outputDir)) { + fs.mkdirSync(outputDir, { recursive: true }); + } + + // get all test files + const sourceFilePaths = getTestFiles(); + + // parse test files + const testFiles: TestFiles = parseTestFiles(sourceFilePaths); + + // count tests in each test group function + const testCounts: TestCounts = countTests(testFiles); + + // generate markdown table from test counts + const markdownTable = generateMDTable(testCounts); + + // write test groups to json file + // writeJsonToFile(outputDir + "/test_groups.json", testFiles); + + // write test count to json file + // writeJsonToFile(outputDir + "/test_counts.json", testCounts); + + // write test count table to markdown file + await writeToFile(outputDir + "/test_counts.md", markdownTable); +} + +main(); diff --git a/scripts/test/test_counter_utils.ts b/scripts/test/test_counter_utils.ts new file mode 100644 index 0000000..35dc53d --- /dev/null +++ b/scripts/test/test_counter_utils.ts @@ -0,0 +1,305 @@ +import * as fs from "fs"; +import * as path from "path"; + +import * as prettier from "prettier"; + +/** + * Represents the structure of an Abstract Syntax Tree (AST). + * + * @interface ASTStructure + * @property {String} kind - The kind of AST node. + * @property {String} text - The text content of the AST node. + * @property {ASTStructure[]} [children] - Optional array of child AST nodes. + */ +export interface ASTStructure { + kind: String; + text: String; + children?: ASTStructure[]; +} + +/** + * Represents a nested map structure for organizing test files. + * + * The structure is as follows: + * - The outer map's keys are strings representing the the file name. + * - The middle map's keys are strings representing the component groups. + * - The inner map's keys are strings representing test categories (Accessibility, Content, etc). + * - The innermost arrays contain strings representing the test names. + */ +export type TestFiles = Map<String, Map<String, Map<String, Array<String>>>>; + +/** + * Represents a nested map structure for counting tests. + * + * - Where the outer map's keys are strings representing the file name. + * - The inner map's keys are strings representing the test group names. + * - The inner map's values are numbers representing the number of tests in the group. + */ +export type TestCounts = Map<string, Map<string, number>>; + +/** + * Extends the global `String` interface with additional methods. + * + * @interface String + * @method capitalizeEachWord - Capitalizes the first letter of each word in the string. + * @returns {string} - The modified string with each word capitalized. + * + * @method matchItText - Matches and processes `it()` text in a specific way. + * @returns {string} - The processed string. + * + * @method matchDescribeText - Matches and processes `describe()` text in a specific way. + * @returns {string} - The processed string. + * + * + * Extends the global `Array` interface with an additional method. + * + * @interface Array + * @method addComponentandTotalRows - Adds a component and total rows to the array. + * @param {TestCounts} testCounts - The test counts to be added. + * @returns {string[]} - The modified array with the added component and total rows. + */ +declare global { + export interface String { + capitalizeEachWord(): string; + matchItText(): string; + matchDescribeText(): string; + sanitizeGroupName(): string; + sanitizeFileName(): string; + isExpression(): boolean; + isItBlock(): boolean; + isDescribeBlock(): boolean; + } + + export interface Array<T> { + addComponentandTotalRows( + testCounts: TestCounts, + testCategories: string[] + ): string[]; + } +} + +/** + * Determines if the string is an `ExpressionStatement`. + * @returns `true` if the string is an `ExpressionStatement`; otherwise, `false`. + */ +String.prototype.isExpression = function (): boolean { + return this === "ExpressionStatement"; +}; + +/** + * Determines if the string is an `it()` block. + * @returns `true` if the string is an `it()` block; otherwise, `false`. + */ +String.prototype.isItBlock = function (): boolean { + return this.startsWith("it(") || this.startsWith("it.skip("); +}; + +/** + * Determines if the string is a `describe()` block. + * @returns `true` if the string is a `describe()` block; otherwise, `false`. + */ +String.prototype.isDescribeBlock = function (): boolean { + return this.startsWith("describe(") || this.startsWith("describe.skip("); +}; + +/** + * Checks if the given AST node represents a describe block expression. + * + * @param node - The AST node to check. + * @returns `true` if the node is a describe block expression, otherwise `false`. + */ +export function isDescribeExpression(node: ASTStructure): boolean { + return node.kind.isExpression() && node.text.isDescribeBlock(); +} + +/** + * Checks if the given AST node represents an it block expression. + * + * @param node - The AST node to check. + * @returns `true` if the node is an it block expression, otherwise `false`. + */ +export function isItExpression(node: ASTStructure): boolean { + return node.kind.isExpression() && node.text.isItBlock(); +} + +/** + * Capitalizes the first letter of each word in the string. + * @returns The modified string with each word capitalized. + */ +String.prototype.capitalizeEachWord = function (): string { + return this.replace(/\b\w/g, (char) => char.toUpperCase()); +}; + +/** + * Sanitizes a string to be used as a group name. + * @returns The sanitized string. + */ +String.prototype.sanitizeGroupName = function (): string { + return this.replaceAll(" Tests", "").replaceAll("SKIPPED ", ""); +}; + +/** + * Sanitizes a string to be used as a file name. + * @returns The sanitized string. + */ +String.prototype.sanitizeFileName = function (): string { + return this.split(".").shift()!.replaceAll("-", " ").capitalizeEachWord(); +}; + +/** + * Matches and processes the text content of a `it()` block. + * @returns The processed string. + */ +String.prototype.matchItText = function (): string { + const match = this.match(/(it(?:\.skip)?)\(([^)]+)\)/); + if (match) { + let result = match[2] + .replaceAll('"', "") + .replace(", async (", "") + .replace(", (", ""); + if (match[1] === "it.skip") { + result = "SKIPPED " + result; + } + return result; + } + return "unknown it"; +}; + +/** + * Matches and processes the text content of a `describe()` block. + * @returns The processed string. + */ +String.prototype.matchDescribeText = function (): string { + const match = this.match(/(describe(?:\.skip)?)\(([^)]+)\)/); + if (match) { + let result = match[2].replaceAll('"', "").replace(", (", ""); + if (match[1] === "describe.skip") { + result = "SKIPPED " + result; + } + return result; + } + return "unknown describe"; +}; + +/** + * Adds component and total rows to the provided data array based on the test counts. + * + * @param data - The array to which the formatted test count rows will be added. + * @param testCounts - An object containing the test counts for each component, organized by test group. + * @returns The updated data array with the components and total rows. + * + * @example + * ```typescript + * const data: string[] = []; + * const testCounts: TestCounts = { + * ComponentA: { + * "Accessibility Tests": 5, + * "Content Tests": 3, + * "Dimensions Tests": 2, + * "Styling Tests": 4, + * "Interaction Tests": 1, + * "Golden Tests": 0, + * "Performance Tests": 2, + * "unorganised": 1, + * }, + * ComponentB: { + * "Accessibility Tests": 2, + * "Content Tests": 1, + * "Dimensions Tests": 3, + * "Styling Tests": 2, + * "Interaction Tests": 4, + * "Golden Tests": 1, + * "Performance Tests": 0, + * "unorganised": 0, + * }, + * }; + * addComponentandTotalRows(data, testCounts); + * console.log(data); + * // Output: + * // [ + * // "| ComponentA | 5 | 3 | 2 | 4 | 1 | 0 | 2 | 1 | 18 |", + * // "| ComponentB | 2 | 1 | 3 | 2 | 4 | 1 | 0 | 0 | 13 |", + * // "| Total Tests | 7 | 4 | 5 | 6 | 5 | 1 | 2 | 1 | 31 |" + * // ] + * ``` + */ +Array.prototype.addComponentandTotalRows = function ( + testCounts: TestCounts, + testCategories: string[] +): string[] { + const groupTotals = new Map<string, number>( + testCategories.map((category) => [category.sanitizeGroupName(), 0]) + ); + groupTotals.set("unorganised", 0); + + // for each component + for (const [componentName, groups] of Object.entries(testCounts)) { + // for each group in the component get the total number of tests + for (const [key, value] of Object.entries(groups)) { + groupTotals.set( + key.sanitizeGroupName(), + groupTotals.get(key.sanitizeGroupName())! + (value as number) + ); + } + + // get the total number of tests for the component + const totalTestsForComponent = Object.values(groups).reduce( + (acc, curr) => (acc as number) + (curr as number), + 0 + ); + + // add the component row + const row = [`| ${componentName}`]; + for (const category of groupTotals.keys()) { + row.push(`${groups[category] || 0}`); + } + // add the total number of tests for the component + row.push(` ${totalTestsForComponent} |`); + this.push(row.join(" | ")); + } + + // get the total number of tests for each group + const totalTests = Array.from(groupTotals.values()).reduce( + (acc, curr) => acc + curr, + 0 + ); + + // add the total row + const row = [`| Total Tests`]; + for (const category of groupTotals.keys()) { + row.push(`${groupTotals.get(category) || 0}`); + } + // add the total number of tests + row.push(` ${totalTests} |`); + this.push(row.join(" | ")); + + return this; +}; + +/** + * Writes a JSON object to a specified file. + * + * @param outputPath - The path where the JSON file will be written. + * @param data - The JSON data to write to the file. + */ +export function writeJsonToFile(outputPath: string, data: any) { + fs.writeFileSync( + path.resolve(outputPath).replace(/^(\.\.(\/|\\|$))+/, ""), + JSON.stringify(data, null, 2), + "utf8" + ); +} + +/** + * Writes the provided data to a file at the specified output path. + * + * @param outputPath - The path where the markdown file will be written. + * @param data - The data to be written to the markdown file. + */ +export async function writeToFile(outputPath: string, data: any) { + fs.writeFileSync( + path.resolve(outputPath).replace(/^(\.\.(\/|\\|$))+/, ""), + await prettier.format(data, { parser: "markdown" }), + "utf8" + ); +} diff --git a/scripts/utils.js b/scripts/utils.js new file mode 100644 index 0000000..ca5dab4 --- /dev/null +++ b/scripts/utils.js @@ -0,0 +1,91 @@ +import { mkdirSync, writeFileSync } from "fs"; +import * as path from "path"; + +/** Custom wrapper for `console.log` to account for --silent arg. + * + * @param {string} string string to be logged. + */ +export const log = string => { + if (!process.argv.slice(2).includes("--silent")) { + console.log(string); + } +}; + +/** Custom wrapper for `writeFileSync` to account for --dry-run arg. + * + * @param {string} pathName Path of file to be written. + * @param {String} contents Contents to be written to file. + */ +export const write = (pathName, contents) => { + if (!process.argv.slice(2).includes("--dry-run")) { + const dir = pathName.split("/"); + dir.pop(); + mkdirSync(path.normalize(dir.join("/")), { recursive: true }); + writeFileSync(path.normalize(pathName), contents); + } +}; + +/** Finds and replaces all instances of a search with replacement in template. + * + * @param {string} search String to be replaced. + * @param {string} replacement Replacement string. + * @param {string} template String to be searched. + * @returns {string} String with replacements made. + */ +export const replaceAllInTemplate = (search, replacement, template) => String(template).replace(new RegExp(search, "g"), replacement); + +/** Capitalizes all words in string. + * + * @param {string} s string to be parsed. + * @returns {string} All words in string capitalized. + */ +export const capitalize = s => + s + .split("-") + .map(w => w.charAt(0).toUpperCase() + w.slice(1)) + .join(""); + +/** + * Inserts a string into another string at a given location. + * + * @param {string} string The string to be added to. + * @param {string} newString The string to be inserted. + * @param {int} index The index for the new string to be insterted at. + * @returns The new string; + */ +export const insert = (string, newString, index) => string.slice(0, index) + newString + string.slice(index, string.length); + +/** + * Converts a Windows path's seperators to '/' + * @param {string} string the path to convert + * @returns The path with all '\' changed to '/' + */ +export const convertPathToUnixStyle = (string) => string.replace(/\\/g, '/'); + +/** + * Converts camelCase to PascalCase + * @param {string} string camelCase string to convert + * @returns PascalCase string representation + */ +export const pascalCase = (string) => pascalCaseFromParts(string.split(/(?=[A-Z])/)); + +/** + * Converts kebab-case to PascalCase + * @param {string} string kebab-case string to convert + * @returns {string} PascalCase representation + */ +export const pascalCaseFromKebab = (string) => pascalCaseFromParts(string.split('-')); + +/** + * Converts a list of strings to a to PascalCase string + * @param {string[]} stringArr Array of words that make up the identifier + * @returns {string} A PascalCase string combining the inputted strings + */ +export const pascalCaseFromParts = (stringArr) => stringArr.reduce((acc, part) => acc + wordToTitleCase(part), "");; + +/** + * Capitalizes the first letter of a string + * @param {string} string + * @returns {string} the inputted string with a Capitalized first letter + */ +export const wordToTitleCase = (string) => (string.slice(0,1).toUpperCase() + string.slice(1)); diff --git a/src/.prettierrc b/src/.prettierrc new file mode 100644 index 0000000..bb6092b --- /dev/null +++ b/src/.prettierrc @@ -0,0 +1,19 @@ +{ + "arrowParens": "avoid", + "bracketSpacing": true, + "endOfLine": "lf", + "htmlWhitespaceSensitivity": "css", + "insertPragma": false, + "jsxBracketSameLine": true, + "jsxSingleQuote": false, + "printWidth": 160, + "proseWrap": "preserve", + "quoteProps": "as-needed", + "requirePragma": false, + "semi": true, + "singleQuote": false, + "tabWidth": 2, + "trailingComma": "none", + "useTabs": false, + "filepath": "eslint.config.mjs" +} diff --git a/src/components/accordion/accordion.styles.js b/src/components/accordion/accordion.styles.js new file mode 100644 index 0000000..0c203e5 --- /dev/null +++ b/src/components/accordion/accordion.styles.js @@ -0,0 +1,31 @@ +import { css } from "lit"; +export default css` + :not([open]) .body { + display: none; + } + :host([open]) .body { + display: block; + } + :host([contained]) .accordion { + border: var(--border-size-small) solid var(--border-default); + } + :host([disabled]) .accordion { + border-color: var(--border-disabled); + color: var(--main-disabled); + } + .title { + font: var(--title-medium); + padding: var(--spacing-large); + display: flex; + align-items: center; + justify-content: space-between; + } + .body { + padding: var(--spacing-small) 0; + } + .body ::slotted(li) { + padding: var(--spacing-small) var(--spacing-large); + list-style-type: none; + font: var(--body-medium); + } +`; diff --git a/src/components/accordion/accordion.ts b/src/components/accordion/accordion.ts new file mode 100644 index 0000000..cab01c8 --- /dev/null +++ b/src/components/accordion/accordion.ts @@ -0,0 +1,57 @@ +import { html, LitElement } from "lit"; +import { customElement, property } from "lit/decorators.js"; +import { Contourable, Interactive } from "../../mixins/mixins.js"; +import styles from "./accordion.styles.js"; +import "../icon/icon.js"; + +// TODO(UX-1334): Accordion closes when clicked inside even if there is a button inside. + +/** + * The accordion is a control element comprising a vertically stacked list of items, such as labels or thumbnails. Each item can be "expanded" or "collapsed" to reveal the content associated with that item. There can be zero expanded items, exactly one, or more than one item expanded at a time, depending on the configuration. + * + * The contents within the tag will be the child of the open accordion. Typically, this would be list items. Custom styles are applied to ```<li>``` elements to match Zeta styles. + * + * @slot - Typically li + * + * @figma https://www.figma.com/file/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?type=design&node-id=3427-67874 + * @storybook https://zeta-ds.web.app/web/storybook/?path=/docs/accordion--docs + */ +@customElement("zeta-accordion") +export class ZetaAccordion extends Contourable(Interactive(LitElement)) { + /** The title of the accordion. */ + @property({ type: String }) accordionTitle?: string; + + /** + * Creates a border around the accordion. + */ + @property({ type: Boolean, reflect: true }) contained: boolean = false; + + /** + * Whether the accordion is open. + */ + @property({ type: Boolean, reflect: true }) open = false; + + private toggleOpen() { + if (!this.disabled) this.open = !this.open; + } + + protected render() { + return html` <div class="accordion" @click=${(_e: Event) => this.toggleOpen()}> + <div class="title"> + <div>${this.accordionTitle}</div> + <zeta-icon .rounded=${this.rounded}>${this.open ? "remove" : "add"}</zeta-icon> + </div> + <div class="body"> + <slot></slot> + </div> + </div>`; + } + + static styles = [styles, super.styles || []]; +} + +declare global { + interface HTMLElementTagNameMap { + "zeta-accordion": ZetaAccordion; + } +} diff --git a/src/components/action-menu/action-menu-button.styles.js b/src/components/action-menu/action-menu-button.styles.js new file mode 100644 index 0000000..e9105dc --- /dev/null +++ b/src/components/action-menu/action-menu-button.styles.js @@ -0,0 +1,7 @@ +import { css } from "lit"; +export default css` + :host { + display: block; + width: fit-content; + } +`; diff --git a/src/components/action-menu/action-menu-button.ts b/src/components/action-menu/action-menu-button.ts new file mode 100644 index 0000000..2143467 --- /dev/null +++ b/src/components/action-menu/action-menu-button.ts @@ -0,0 +1,101 @@ +import { customElement, property, query } from "lit/decorators.js"; +import styles from "./action-menu-button.styles.js"; +import { html, LitElement } from "lit"; +import { Contourable, Flavored, Size } from "../../mixins/mixins.js"; +import type { ZetaDroppable } from "../dropdown/droppable.js"; +import "../button/icon-button/icon-button.js"; +import "../dropdown/menu-item/dropdown-menu-item.js"; +import "../dropdown/droppable.js"; +import type { ZetaIconName } from "@zebra-fed/zeta-icons"; +import type { ZetaDropdownItem } from "../dropdown/dropdown-menu/dropdown-menu-button.js"; + +/** Zeta Action Menu Button places a button that when clicked opens an action menu containing the items passed into it through the items prop. + * + * @figma https://www.figma.com/file/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?node-id=22391-10146 + * @storybook https://zeta-ds.web.app/web/storybook/?path=/docs/dropdown--docs + */ +@customElement("zeta-action-menu-button") +export class ZetaActionMenuButton extends Contourable(Flavored(Size(LitElement))) { + /** Controls the state of the dropdown menu. */ + @property({ type: Boolean }) open: boolean = false; + + /** Array of action items */ + @property({ type: Array }) items: Array<ZetaDropdownItem> = [ + { + label: "Auto Item", + icon: "star", + onClick: () => { + console.log("Auto Item clicked"); + } + } + ]; + + /** The icon to be displayed on the button */ + @property({ type: String }) icon: ZetaIconName = "more_vertical"; + + /** The alignment of the droppable relative to the action menu. Defaults start if left undefined.*/ + + @property({ type: String }) alignment?: "start" | "end" | "center"; + + /** The direction of the droppable relative to the anchor. Defaults to bottom if left undefined.*/ + @property({ type: String }) direction?: "left" | "right" | "bottom" | "top" = "bottom"; + + @query("#anchor") anchor!: HTMLElement; + + @query("zeta-droppable") droppable!: ZetaDroppable; + + protected firstUpdated() { + this.droppable.anchor = this.anchor; + } + + private handleClick() { + this.open = !this.open; + if (this.open) { + document.body.style.overflow = "hidden"; + } else { + document.body.style.overflow = "auto"; + } + } + + private handleOutsideClick(e: Event) { + if (this.open && !this.contains(e.target as Node)) { + this.open = false; + document.body.style.overflow = "auto"; + } + } + + private renderItems() { + return this.items.map(item => { + return html`<zeta-dropdown-menu-item @click=${item.onClick} ?rounded=${this.rounded} + ><zeta-icon slot="icon">${item.icon}</zeta-icon>${item.label}</zeta-dropdown-menu-item + >`; + }); + } + + protected render() { + document.addEventListener("click", this.handleOutsideClick.bind(this)); + return html` + <zeta-icon-button + id="anchor" + @click=${() => { + this.handleClick(); + }} + .size=${this.size} + ?rounded=${this.rounded} + .flavor=${this.flavor} + >${this.icon}</zeta-icon-button + > + <zeta-droppable ?open=${this.open} ?rounded=${this.rounded} .alignment=${this.alignment} .direction=${this.direction} .anchor=${this.anchor} + >${this.renderItems()}</zeta-droppable + > + `; + } + + static styles = [styles, super.styles || []]; +} + +declare global { + interface HTMLElementTagNameMap { + "zeta-action-menu-button": ZetaActionMenuButton; + } +} diff --git a/src/components/avatar-rail/avatar-rail.styles.js b/src/components/avatar-rail/avatar-rail.styles.js new file mode 100644 index 0000000..47d2a02 --- /dev/null +++ b/src/components/avatar-rail/avatar-rail.styles.js @@ -0,0 +1,56 @@ +import { css } from "lit"; + +export default css` + :host { + display: flex; + width: fit-content; + max-width: 100%; + overflow-x: auto; + height: min-content; + gap: var(--spacing-small); + } + + :host([show-close]) { + --show-close: "block"; + } + + :host([size="xxxs"]) { + --avatar-size: 24px; + } + + :host([size="xxs"]) { + --avatar-size: 32px; + } + + :host([size="xs"]) { + --avatar-size: 36px; + } + + :host([size="s"]) { + --avatar-size: 40px; + } + + :host([size="m"]) { + --avatar-size: var(--default-avatar-size); + } + + :host([size="l"]) { + --avatar-size: 64px; + } + + :host([size="xl"]) { + --avatar-size: 80px; + } + + :host([size="xxl"]) { + --avatar-size: 120px; + } + + :host([size="xxxl"]) { + --avatar-size: 200px; + } + + ::slotted(:not(zeta-avatar)) { + display: none; + } +`; diff --git a/src/components/avatar-rail/avatar-rail.ts b/src/components/avatar-rail/avatar-rail.ts new file mode 100644 index 0000000..2a35594 --- /dev/null +++ b/src/components/avatar-rail/avatar-rail.ts @@ -0,0 +1,37 @@ +import { html, LitElement } from "lit"; +import styles from "./avatar-rail.styles.js"; +import { customElement, property } from "lit/decorators.js"; +import type { AvatarSize } from "../avatar/avatar-size.js"; + +/** Avatar rail is a container for multiple avatars. + * + * @slot {zeta-avatar-rail[]} - The avatars to be displayed in the rail. + * + * @figma https://www.figma.com/design/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?node-id=229-2&node-type=canvas&m=dev + * @storybook https://zeta-ds.web.app/web/storybook/index.html?path=/docs/avatar-rail--docs + */ +@customElement("zeta-avatar-rail") +export class ZetaAvatarRail extends LitElement { + /** + * Shows the close icon on all avatars in the rail. This will be overridden by the individual avatar's `show-close` attribute. + * When clicked, an `avatar-close` event will be fired which can be listened to by adding a listener for the `avatar-close` event on the rail. + */ + @property({ type: Boolean, reflect: true, attribute: "show-close" }) showClose: boolean = false; + + /** + * The size of the avatars in the rail. This will be overridden by the individual avatar's `size` attribute. + */ + @property({ type: String, reflect: true }) size?: AvatarSize; + + protected render() { + return html`<slot></slot> `; + } + + static styles = [super.styles ?? [], styles]; +} + +declare global { + interface HTMLElementTagNameMap { + "zeta-avatar-rail": ZetaAvatarRail; + } +} diff --git a/src/components/avatar/avatar-size.ts b/src/components/avatar/avatar-size.ts new file mode 100644 index 0000000..9dc558a --- /dev/null +++ b/src/components/avatar/avatar-size.ts @@ -0,0 +1 @@ +export type AvatarSize = "xxxs" | "xxs" | "xs" | "s" | "m" | "l" | "xl" | "xxl" | "xxxl"; diff --git a/src/components/avatar/avatar.styles.js b/src/components/avatar/avatar.styles.js new file mode 100644 index 0000000..a525bde --- /dev/null +++ b/src/components/avatar/avatar.styles.js @@ -0,0 +1,109 @@ +import { css } from "lit"; +export default css` + :host { + --default-avatar-size: 48px; + --computed-avatar-size: var(--avatar-size, var(--default-avatar-size)); + display: block; + position: relative; + width: var(--computed-avatar-size); + min-width: var(--computed-avatar-size); + height: var(--computed-avatar-size); + font-size: calc(var(--computed-avatar-size) * 0.4); + --border-width: calc(var(--computed-avatar-size) * 0.05); + --icon-size: calc(var(--computed-avatar-size) * 0.5); + } + + :host([size="xxxs"]) { + --avatar-size: 24px; + } + + :host([size="xxs"]) { + --avatar-size: 32px; + } + + :host([size="xs"]) { + --avatar-size: 36px; + } + + :host([size="s"]) { + --avatar-size: 40px; + } + + :host([size="m"]) { + --avatar-size: var(--default-avatar-size); + } + + :host([size="l"]) { + --avatar-size: 64px; + } + + :host([size="xl"]) { + --avatar-size: 80px; + } + + :host([size="xxl"]) { + --avatar-size: 120px; + } + + :host([size="xxxl"]) { + --avatar-size: 200px; + } + + :host([show-ring]) .avatar { + border: var(--border-width) solid var(--border-default); + box-sizing: border-box; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + } + + .avatar { + border-radius: var(--radius-full); + color: var(--avatar-initials-color, var(--main-inverse)); + font-weight: 500; + background-color: var(--avatar-color, var(--surface-avatar-purple)); + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + position: relative; + text-transform: uppercase; + } + + ::slotted(img) { + border-radius: var(--radius-full); + height: 100%; + width: 100%; + display: flex; + object-fit: cover; + } + + ::slotted(zeta-icon) { + color: var(--main-inverse); + } + + .close, + .status { + position: absolute; + border-radius: var(--radius-full); + border: calc(var(--computed-avatar-size) * 0.02) solid var(--surface-default); + right: 0; + } + + :host([show-close]) { + --show-close: "show"; + } + + .close { + visibility: var(--show-close, hidden); + top: 0; + --icon-size: calc(var(--computed-avatar-size) * 0.3); + --icon-color: var(--main-inverse); + background-color: var(--main-disabled); + cursor: pointer; + } + + .status { + bottom: 0; + } +`; diff --git a/src/components/avatar/avatar.ts b/src/components/avatar/avatar.ts new file mode 100644 index 0000000..b0f13c8 --- /dev/null +++ b/src/components/avatar/avatar.ts @@ -0,0 +1,66 @@ +import { customElement, property } from "lit/decorators.js"; +import { LitElement, html } from "lit"; +import styles from "./avatar.styles.js"; +import "../icon/icon.js"; +import "../badges/indicators/indicators.js"; +import { ZetaCloseEvent } from "../../events.js"; +import type { AvatarSize } from "./avatar-size.js"; + +/** + * An avatar is a visual representation of a user or entity. + * + * @cssproperty --avatar-color - The color of the avatar + * @cssproperty --avatar-initials-color - The color of the initials + * @slot - The content of the avatar. Should be an img element, a zeta-icon, or text. + * @slot status - The content of the status slot. Usually used for indicators or badges. + * @attr {boolean} show-ring - Shows the ring around the avatar. + * @attr {boolean} show-close - Shows the close icon. + * @event {CustomEvent<ZetaCloseEvent>} ZetaCloseEvent:close - Fired when the close icon is clicked. + * + * @figma https://www.figma.com/file/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?type=design&node-id=20816-388 + * @storybook https://zeta-ds.web.app/web/storybook/?path=/docs/avatar--docs + */ + +@customElement("zeta-avatar") +export class ZetaAvatar extends LitElement { + /** + * The size of the avatar. + * Possible values are "xxxs", "xxs", "xs", "s", "m", "l", "xl", "xxl", "xxxl". + */ + @property({ type: String, reflect: true }) size?: AvatarSize; + + /** + * Shows the ring around the avatar. + */ + @property({ type: Boolean, reflect: true, attribute: "show-ring" }) showRing: boolean = false; + + /** + * Shows the close icon. + */ + @property({ type: Boolean, reflect: true, attribute: "show-close" }) showClose: boolean = false; + + protected render() { + return html` + <div class="avatar"> + <slot id="CONTENT_SLOT"></slot> + </div> + <div + class="close" + @click=${() => { + this.dispatchEvent(new ZetaCloseEvent().toEvent()); + }} + > + <zeta-icon>close</zeta-icon> + </div> + <div class="status"><slot name="status" id="STATUS_SLOT"></slot></div> + `; + } + + static styles = [styles]; +} + +declare global { + interface HTMLElementTagNameMap { + "zeta-avatar": ZetaAvatar; + } +} diff --git a/src/components/badges/badges.ts b/src/components/badges/badges.ts new file mode 100644 index 0000000..31d6ff3 --- /dev/null +++ b/src/components/badges/badges.ts @@ -0,0 +1,6 @@ +export * from "./indicators/indicators.js"; +export * from "./priority-pill/priority-pill.js"; +export * from "./status-label/status-label.js"; +export * from "./tag/tag.js"; +export * from "./label/label.js"; +export * from "./workcloud-indicator/workcloud-indicator.js"; diff --git a/src/components/badges/indicators/indicators.styles.js b/src/components/badges/indicators/indicators.styles.js new file mode 100644 index 0000000..551e10e --- /dev/null +++ b/src/components/badges/indicators/indicators.styles.js @@ -0,0 +1,67 @@ +import { css } from "lit"; +export default css` + :host([size="small"]) { + .container { + width: var(--spacing-small); + height: var(--spacing-small); + } + + zeta-icon { + --icon-size: 0; + } + } + + :host([size="medium"]) .container { + width: var(--spacing-medium); + height: var(--spacing-medium); + + .icon { + position: absolute; + right: 1px; + } + zeta-icon { + --icon-size: 8px; + } + } + + :host([size="large"]) .container { + width: var(--spacing-xl); + height: var(--spacing-xl); + + zeta-icon { + --icon-size: 12px; + } + } + + :host([inverse]) .container { + border: var(--border-size-medium) solid var(--main-default); + } + + :host { + width: auto; + display: block; + + .container { + border-radius: var(--radius-full); + width: auto; + display: flex; + justify-content: center; + align-items: center; + font: var(--label-indicator); + color: var(--main-inverse); + aspect-ratio: 1 / 1; + } + } + + .container.icon { + background-color: var(--main-primary); + } + + .container.notification { + background-color: var(--main-negative); + } + + :host zeta-icon { + --icon-color: var(--main-inverse); + } +`; diff --git a/src/components/badges/indicators/indicators.ts b/src/components/badges/indicators/indicators.ts new file mode 100644 index 0000000..55ec99c --- /dev/null +++ b/src/components/badges/indicators/indicators.ts @@ -0,0 +1,76 @@ +import { html, LitElement } from "lit"; +import { customElement, property } from "lit/decorators.js"; +import styles from "./indicators.styles.js"; +import { type ZetaIconName } from "@zebra-fed/zeta-icons"; +import { Contourable, Size } from "../../../mixins/mixins.js"; +import "../../icon/icon.js"; + +/** Indicators are used to show the status of a user or any messages/notifications they might have. + * + * @figma https://www.figma.com/file/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?type=design&node-id=22000-10045&mode=design&t=6mhOcUUr3tgxxFdd-0 + * @figma https://www.figma.com/file/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?type=design&node-id=22000-10072&mode=design&t=6mhOcUUr3tgxxFdd-0 + * + * @storybook https://zeta-ds.web.app/web/storybook/?path=/docs/badges--docs + */ +@customElement("zeta-indicator") +export class ZetaIndicator extends Size(Contourable(LitElement)) { + /** + * Whether indicator is to be on an inverse background. + * + * Adds an inverse color border to the indicator. + */ + @property({ type: Boolean, reflect: true }) inverse: boolean = false; + + /** + * Icon to be shown on icon type indicator. + * + * Full list of icons can be found at {@link https://zeta-icons.web.app/ Zeta Icons}. + */ + @property({ type: String }) icon: ZetaIconName = "star"; + + /** Whether to render as a notification or icon indicator. */ + @property({ type: String }) type: "icon" | "notification" = "notification"; + + @property({ type: String, reflect: true }) value: string | true | false = false; + + static styles = [super.styles ?? [], styles]; + + private getBody(value: string | boolean) { + if (this.type == "icon") { + return html`<zeta-icon .rounded=${this.rounded}>${this.icon}</zeta-icon> `; + } else { + return html`${value}`; + } + } + + protected override render() { + const value = this.value === true || !this.value ? "" : this.value; + return html` <div class="container ${this.type} ${value.length ? "expand" : ""}">${this.size !== "small" ? this.getBody(value) : ""}</div> `; + } +} + +/** Indicator with error red background, and an icon foreground. */ +@customElement("zeta-icon-indicator") +export class ZetaIconIndicator extends ZetaIndicator { + constructor() { + super(); + this.type = "icon"; + } +} + +/** Indicator with primary blue background and text / number foreground. */ +@customElement("zeta-notification-indicator") +export class ZetaNotificationIndicator extends ZetaIndicator { + constructor() { + super(); + this.type = "notification"; + } +} + +declare global { + interface HTMLElementTagNameMap { + "zeta-notification-indicator": ZetaNotificationIndicator; + "zeta-icon-indicator": ZetaIconIndicator; + "zeta-indicator": ZetaIndicator; + } +} diff --git a/src/components/badges/label/label.styles.js b/src/components/badges/label/label.styles.js new file mode 100644 index 0000000..6ee141a --- /dev/null +++ b/src/components/badges/label/label.styles.js @@ -0,0 +1,43 @@ +import { css } from "lit"; +export default css` + :host([status="info"]) .container { + background: var(--surface-info); + } + + :host([status="positive"]) .container { + background: var(--surface-positive); + } + + :host([status="warning"]) .container { + background: var(--surface-warning); + } + + :host([status="negative"]) .container { + background: var(--surface-negative); + } + + .container, + :host([status="neutral"]) .container { + background: var(--main-light); + .text { + color: var(--main-default); + } + } + + :host(:not([status="neutral"])) .container .text { + color: var(--main-inverse); + } + + .container { + display: flex; + align-items: center; + justify-content: center; + white-space: nowrap; + width: fit-content; + + .text { + padding: 2px var(--spacing-minimum); + font: var(--label-small); + } + } +`; diff --git a/src/components/badges/label/label.ts b/src/components/badges/label/label.ts new file mode 100644 index 0000000..a472912 --- /dev/null +++ b/src/components/badges/label/label.ts @@ -0,0 +1,42 @@ +import { html, LitElement } from "lit"; +import { customElement, property } from "lit/decorators.js"; +import styles from "./label.styles.js"; +import { Contourable } from "../../../mixins/mixins.js"; + +/** + * To help some information, labels, or errors stand out, we present them with badges. They can look like buttons, but users can’t select them. They just guide users to things they should pay attention to. + * + * children: + * * Text + * + * @figma https://www.figma.com/file/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?type=design&node-id=21926-2099 + * @storybook https://zeta-ds.web.app/web/storybook/?path=/docs/badges--docs + */ +@customElement("zeta-label") +export class ZetaLabel extends Contourable(LitElement) { + /** Type of text label. + * + * @defaultValue `BannerStatus.default` */ + @property({ type: String, reflect: true }) status: "info" | "positive" | "warning" | "negative" | "neutral" = "neutral"; + + /** Text displayed on label. + * + * Can also be slotted. */ + @property({ type: String }) text?: string; + + static styles = [styles, super.styles ?? []]; + + protected override render() { + return html` + <div class="container"> + <div class="text">${this.text && this.text}<slot></slot></div> + </div> + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + "zeta-label": ZetaLabel; + } +} diff --git a/src/components/badges/priority-pill/priority-pill.styles.js b/src/components/badges/priority-pill/priority-pill.styles.js new file mode 100644 index 0000000..9c42e17 --- /dev/null +++ b/src/components/badges/priority-pill/priority-pill.styles.js @@ -0,0 +1,33 @@ +import { css } from "lit"; +export default css` + .container { + display: flex; + display: inline-flex; + align-items: center; + background: var(--surface-primary-subtle); + white-space: nowrap; + line-height: var(--spacing-xl); + font: var(--body-small); + + > .number { + display: flex; + width: var(--spacing-3xl); + height: var(--spacing-3xl); + padding: 0; + justify-content: center; + align-items: center; + text-align: center; + background: var(--main-primary); + color: var(--main-inverse); + } + + > .text { + padding: var(--spacing-minimum) var(--spacing-small); + } + } + + :host([rounded]) > .container, + :host([rounded]) > .container > .number { + border-radius: var(--radius-full); + } +`; diff --git a/src/components/badges/priority-pill/priority-pill.ts b/src/components/badges/priority-pill/priority-pill.ts new file mode 100644 index 0000000..471ad39 --- /dev/null +++ b/src/components/badges/priority-pill/priority-pill.ts @@ -0,0 +1,44 @@ +import { html, LitElement } from "lit"; +import { customElement, property } from "lit/decorators.js"; +import styles from "./priority-pill.styles.js"; +import { Contourable } from "../../../mixins/mixins.js"; + +/** + * This badge is used to indicate the order of importance. + * + * Slotted children: + * * Number + * * Text + * + * @figma https://www.figma.com/file/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?type=design&node-id=22000-15955 + * @storybook https://zeta-ds.web.app/web/storybook/?path=/docs/badges--docs + */ +@customElement("zeta-priority-pill") +export class ZetaPriorityPill extends Contourable(LitElement) { + /** Text of Priority. + * + * Can also be slotted. */ + @property({ type: String }) text?: string; + + /** Number shown at start of component. + * + * Can also be slotted. */ + @property() number?: string | number; + + static styles = [styles, super.styles ?? []]; + + protected override render() { + return html` + <div class="container"> + <div class="number">${this.number}</div> + <div class="text">${this.text}</div> + </div> + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + "zeta-priority-pill": ZetaPriorityPill; + } +} diff --git a/src/components/badges/status-label/status-label.styles.js b/src/components/badges/status-label/status-label.styles.js new file mode 100644 index 0000000..47c2bb3 --- /dev/null +++ b/src/components/badges/status-label/status-label.styles.js @@ -0,0 +1,66 @@ +import { css } from "lit"; +export default css` + .container { + display: inline-flex; + justify-content: center; + align-items: center; + gap: var(--spacing-small); + border-radius: var(--radius-minimal); + padding-top: 1px; + padding-bottom: 1px; + border: var(--border-size-small) solid; + + > .icon-container { + padding-left: calc(var(--spacing-small) - var(--border-size-small)); + max-height: var(--spacing-xl); + --icon-size: 20px; + } + > .icon-container, + > .icon-container div { + display: inline-flex; + justify-content: center; + align-items: center; + } + > .text { + color: var(--main-default); + font: var(--body-small); + line-height: 1; + padding-right: calc(var(--spacing-small) - var(--border-size-small)); + } + } + :host([rounded]) > .container { + border-radius: var(--radius-full); + } + + .container, + :host([status="neutral"]) > .container, + :host([status="neutral"]) > .container svg { + fill: var(--main-subtle); + border-color: var(--border-default); + background: var(--main-light); + } + :host([status="info"]) > .container, + :host([status="info"]) > .container svg { + border-color: var(--border-info); + background: var(--surface-info-subtle); + fill: var(--main-info); + } + :host([status="positive"]) > .container, + :host([status="positive"]) > .container svg { + border-color: var(--border-positive); + background: var(--surface-positive-subtle); + fill: var(--main-positive); + } + :host([status="warning"]) > .container, + :host([status="warning"]) > .container svg { + border-color: var(--border-warning); + background: var(--surface-warning-subtle); + fill: var(--main-warning); + } + :host([status="negative"]) > .container, + :host([status="negative"]) > .container svg { + border-color: var(--border-negative); + background: var(--surface-negative-subtle); + fill: var(--main-negative); + } +`; diff --git a/src/components/badges/status-label/status-label.ts b/src/components/badges/status-label/status-label.ts new file mode 100644 index 0000000..3f46e82 --- /dev/null +++ b/src/components/badges/status-label/status-label.ts @@ -0,0 +1,61 @@ +import { html, LitElement, svg } from "lit"; +import { customElement, property } from "lit/decorators.js"; +import styles from "./status-label.styles.js"; +import { type ZetaIconName } from "@zebra-fed/zeta-icons"; +import { Contourable } from "../../../mixins/mixins.js"; +import "../../icon/icon.js"; +import { styleMap } from "lit/directives/style-map.js"; +/** + * To help some information, labels, or errors stand out, we present them with badges. They can look like buttons, but users can’t select them. They just guide users to things they should pay attention to. + * + * Slotted children: + * * Text + * * Icon + * + * @figma https://www.figma.com/file/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?type=design&node-id=21836-37274 + * @storybook https://zeta-ds.web.app/web/storybook/?path=/docs/badges--docs + */ +@customElement("zeta-status-label") +export class ZetaStatusLabel extends Contourable(LitElement) { + /** Type of status label.*/ + @property({ type: String, reflect: true }) status: "info" | "positive" | "warning" | "negative" | "neutral" = "neutral"; + + /** Text displayed on label. + * + * Can also be slotted. */ + @property({ type: String }) text?: string; + + /** Icon leading the component. @see {ZetaIconName}. + * + * @defaultValue `undefined`. This will render an indicator circle. + */ + @property({ type: String }) icon?: ZetaIconName; + static styles = [super.styles ?? [], styles]; + + protected override render() { + const icon = this.icon + ? html`<zeta-icon + .rounded=${this.rounded} + style=${styleMap({ + "--icon-color": `var(--icon-" + ${this.status} + ")` + })} + >${this.icon}</zeta-icon + >` + : svg` + <svg xmlns="http://www.w3.org/2000/svg" width="8" height="20" viewBox="0 0 8 8" > + <circle cx="4" cy="4" r="4" /> + </svg>`; + return html` + <div class="container"> + <div class="icon-container">${icon}</div> + <div class="text">${this.text ? this.text : html`<slot class="text" name="text"></slot>`}</div> + </div> + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + "zeta-status-label": ZetaStatusLabel; + } +} diff --git a/src/components/badges/tag/tag.styles.js b/src/components/badges/tag/tag.styles.js new file mode 100644 index 0000000..575b649 --- /dev/null +++ b/src/components/badges/tag/tag.styles.js @@ -0,0 +1,37 @@ +import { css } from "lit"; +export default css` + :host([point="left"]) > .tag .point { + transform: rotate(180deg); + } + + :host([point="left"]) > .tag { + flex-direction: row-reverse; + } + + :host([point="left"][rounded]) > .tag .text { + border-top-right-radius: 2px; + border-bottom-right-radius: 2px; + } + + :host([point="right"][rounded]) > .tag .text { + border-top-left-radius: 2px; + border-bottom-left-radius: 2px; + } + + .tag { + display: flex; + align-items: center; + width: fit-content; + height: fit-content; + } + + .point { + fill: var(--main-light); + } + + .text { + background: var(--main-light); + padding: var(--spacing-minimum) var(--spacing-small); + font: var(--body-small); + } +`; diff --git a/src/components/badges/tag/tag.ts b/src/components/badges/tag/tag.ts new file mode 100644 index 0000000..164fe10 --- /dev/null +++ b/src/components/badges/tag/tag.ts @@ -0,0 +1,45 @@ +import { html, LitElement, svg } from "lit"; +import { customElement, property } from "lit/decorators.js"; +import styles from "./tag.styles.js"; +import { Contourable } from "../../../mixins/mixins.js"; + +/** ZetaTag web component. + * + * Tags are used to draw attention to a specific area or information. + * The arrow shape helps direct the users attention to the desired place. + * + * @figma https://www.figma.com/file/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?type=design&node-id=22000-13170 + * @storybook https://zeta-ds.web.app/web/storybook/?path=/docs/badges--docs + */ +@customElement("zeta-tag") +export class ZetaTag extends Contourable(LitElement) { + /** Text displayed in the tag. */ + @property({ type: String }) text: string = ""; + + /** Direction of the tag point. */ + @property({ type: String, reflect: true }) point: "right" | "left" = "right"; + + static styles = [styles, super.styles ?? []]; + + protected render() { + const point = html` <svg class="point" xmlns="http://www.w3.org/2000/svg" width="12" height="28" viewBox="0 0 12 28" fill="none"> + ${this.rounded + ? svg`<path d="M10.8844 12.6984L0 0V28L10.8844 15.3016C11.5263 14.5526 11.5263 13.4474 10.8844 12.6984Z" /> +` + : svg`<path d="M12 14L0 0V28L12 14Z" />`} + </svg>`; + + return html` + <div class="tag" role="note" aria-label=${this.text}> + <span class="text">${this.text}</span> + ${point} + </div> + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + "zeta-tag": ZetaTag; + } +} diff --git a/src/components/badges/workcloud-indicator/workcloud-indicator.styles.js b/src/components/badges/workcloud-indicator/workcloud-indicator.styles.js new file mode 100644 index 0000000..ab52a68 --- /dev/null +++ b/src/components/badges/workcloud-indicator/workcloud-indicator.styles.js @@ -0,0 +1,86 @@ +import { css } from "lit"; +export default css` + :host([priority="urgent"]) { + .number { + background: var(--surface-negative); + } + .container { + background: var(--surface-negative-subtle); + } + } + + :host([priority="high"]) { + .number { + background: var(--border-warning); + } + .container { + background: var(--surface-warning-subtle); + } + } + + :host([priority="medium"]) { + .number { + background: var(--surface-primary); + } + .container { + background: var(--surface-primary-subtle); + } + } + + :host([priority="low"]) { + .number { + background: var(--surface-positive); + } + .container { + background: var(--surface-positive-subtle); + } + } + + :host([size="medium"]) { + .text { + padding: var(--spacing-0-5) var(--spacing-small) var(--spacing-0-5) var(--spacing-minimum); + } + .text, + .number { + font: var(--body-small); + line-height: 1; + } + .number { + width: var(--spacing-2xl); + height: var(--spacing-2xl); + } + } + + :host([size="small"]) { + .text, + .number { + font: var(--body-x-small); + line-height: 1; + } + .number { + width: var(--spacing-xl); + height: var(--spacing-xl); + } + } + + :host([size="xs"]) { + .text { + display: none; + } + .number { + font: var(--body-x-small); + width: var(--spacing-xl); + height: var(--spacing-xl); + color: var(--main-inverse); + } + } + + :host { + line-height: 1; + .text { + height: auto; + text-transform: capitalize; + padding: 0 var(--spacing-small) 0 var(--spacing-minimum); + } + } +`; diff --git a/src/components/badges/workcloud-indicator/workcloud-indicator.ts b/src/components/badges/workcloud-indicator/workcloud-indicator.ts new file mode 100644 index 0000000..0813f18 --- /dev/null +++ b/src/components/badges/workcloud-indicator/workcloud-indicator.ts @@ -0,0 +1,48 @@ +import { customElement, property } from "lit/decorators.js"; +import type { LitElement } from "lit"; +import { html } from "lit"; +import { type Constructor } from "../../../mixins/mixins.js"; +import styles from "./workcloud-indicator.styles.js"; +import { ZetaPriorityPill } from "../priority-pill/priority-pill.js"; + +const OverwriteStyles = <T extends Constructor<LitElement>>(superClass: T) => { + class OverwriteStylesClass extends superClass { + static styles = [(superClass as unknown as typeof LitElement).styles ?? [], styles]; + } + return OverwriteStylesClass as Constructor<LitElement> & T; +}; + +const ZetaWorkcloudIndicatorBase = OverwriteStyles(ZetaPriorityPill); + +@customElement("zeta-workcloud-indicator") +export class ZetaWorkcloudIndicator extends ZetaWorkcloudIndicatorBase { + /** + * Size variant + */ + @property({ type: String, reflect: true }) size: "medium" | "small" | "xs" = "medium"; + /** + * Priority level + */ + @property({ type: String, reflect: true }) priority: "urgent" | "high" | "medium" | "low" = "low"; + /** + * Number of indications + */ + @property({ type: Number }) number: number = 0; + + protected render() { + // In case there are more than 2 symbols, otherwise value overflows + const formattedNumber = this.number > 99 ? "99+" : this.number; + return html` + <div class="container"> + <span aria-label="number" role="note" class="number">${this.priority === "urgent" ? "U" : formattedNumber}</span> + <span aria-label=${this.priority} role="status" class="text">${this.priority}</span> + </div> + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + "zeta-workcloud-indicator": ZetaWorkcloudIndicator; + } +} diff --git a/src/components/base-toggle-form-element.styles.js b/src/components/base-toggle-form-element.styles.js new file mode 100644 index 0000000..508b8f5 --- /dev/null +++ b/src/components/base-toggle-form-element.styles.js @@ -0,0 +1,69 @@ +import { css } from "lit"; +export default css` + :host label { + display: flex; + width: fit-content; + gap: var(--spacing-medium); + align-items: center; + position: relative; + } + + input { + position: absolute; + appearance: none; + height: 0; + width: 0; + visibility: hidden; + margin: 0; + top: 50%; + left: 50%; + } + + label { + font: var(--body-medium); + } + + *[part="icon"] { + display: none; + --icon-size: 20px; + } + + .container { + display: flex; + align-items: center; + background: var(--surface-default); + position: relative; + border: var(--border-size-medium) solid var(--main-subtle); + height: var(--spacing-large); + width: var(--spacing-large); + justify-content: center; + } + + :host label:hover .container { + border-color: var(--border-hover); + } + + :host([indeterminate]:not([disabled])) label, + :host([checked]:not([disabled])) label { + .container { + border-color: var(--surface-primary); + } + &:hover .container { + border-color: var(--border-hover); + } + } + + :host([indeterminate]) *[part="icon"], + :host([checked]) *[part="icon"] { + /* TODO add animation */ + --icon-color: var(--main-inverse); + display: block; + } + + :host([disabled]) { + .container { + background-color: var(--surface-disabled); + border-color: var(--surface-disabled); + } + } +`; diff --git a/src/components/base-toggle-form-element.ts b/src/components/base-toggle-form-element.ts new file mode 100644 index 0000000..2634873 --- /dev/null +++ b/src/components/base-toggle-form-element.ts @@ -0,0 +1,54 @@ +import { html, LitElement } from "lit"; +import { Contourable, Interactive } from "../mixins/mixins.js"; +import styles from "./base-toggle-form-element.styles.js"; +import { FormField } from "../mixins/form-field.js"; +import "./icon/icon"; + +export abstract class BaseToggleFormElement extends FormField(Interactive(Contourable(LitElement))) { + static override shadowRootOptions: ShadowRootInit = { + ...LitElement.shadowRootOptions, + mode: "open", + delegatesFocus: true + }; + + override handleChange(event: Event): void { + this.dispatchEvent(new Event(event.type, event)); + } + + override focus() { + this.input.focus(); + } + + override blur() { + this.input.blur(); + } + + key(e: KeyboardEvent, type: "down" | "up") { + if (type === "up") { + if (e.key === " ") { + this.input.click(); + } + } + } + + protected render() { + return html` + <label> + <div + class="container interactive-target" + tabindex="${this.disabled ? "-1" : this.tabIndex}" + @keydown=${(e: KeyboardEvent) => this.key(e, "down")} + @keyup=${(e: KeyboardEvent) => this.key(e, "up")} + > + ${this.type === "checkbox" + ? html`<zeta-icon part="icon" ?rounded=${this.rounded}> ${this.indeterminate ? "remove" : "check_mark"} </zeta-icon>` + : html`<div part="icon"></div>`} + </div> + <slot></slot> + ${super.render()} + </label> + `; + } + + static styles = [styles, super.styles || []]; +} diff --git a/src/components/bottom-sheets/bottom-sheet.styles.js b/src/components/bottom-sheets/bottom-sheet.styles.js new file mode 100644 index 0000000..59d54b3 --- /dev/null +++ b/src/components/bottom-sheets/bottom-sheet.styles.js @@ -0,0 +1,76 @@ +import { css } from "lit"; +export default css` + :host { + display: block; + position: relative; + overflow-y: hidden; + } + + :host > .container { + transition: transform 250ms ease; + } + + :host(:not([isExpanded])) > .container { + transform: translateY(100%); + } + + :host([isExpanded]) > .container { + transform: translateY(0); + } + + .container { + display: flex; + flex-direction: column; + align-items: start; + justify-content: center; + padding: var(--spacing-small) var(--spacing-small) var(--spacing-5xl) var(--spacing-small); + border-top-right-radius: var(--radius-large); + border-top-left-radius: var(--radius-large); + max-height: var(--bottom-sheet-max-height, 90vh); + } + + .handle { + display: inherit; + background-color: var(--surface-disabled); + width: var(--spacing-4xl); + height: var(--spacing-minimum); + border-radius: var(--radius-full); + align-self: center; + } + + :host([headerAlignment="start"]) .header { + justify-content: flex-start; + } + + :host([headerAlignment="center"]) .header { + justify-content: center; + } + + .header { + display: inherit; + width: calc(100% - var(--spacing-xl) * 2); + justify-content: flex-start; + color: var(--main-default); + padding: var(--spacing-xl); + font: var(--title-large); + } + + .content { + width: 100%; + overflow-y: auto; + display: flex; + flex-direction: column; + justify-content: stretch; + } + + .isGenericContent { + display: block; + } + + .isGrid { + display: grid; + justify-content: space-around; + grid-template-columns: auto auto auto; + gap: var(--spacing-5xl); + } +`; diff --git a/src/components/bottom-sheets/bottom-sheet.ts b/src/components/bottom-sheets/bottom-sheet.ts new file mode 100644 index 0000000..52018e8 --- /dev/null +++ b/src/components/bottom-sheets/bottom-sheet.ts @@ -0,0 +1,65 @@ +import { html, LitElement } from "lit"; +import { customElement, property, queryAssignedElements, state } from "lit/decorators.js"; +import styles from "./bottom-sheet.styles.js"; +import { ZetaGridMenuItem } from "../grid-menu-item/grid-menu-item.js"; +import { ZetaListItem } from "../list/list.js"; + +/** Bottom sheets are surfaces containing supplementary content that are anchored to the bottom of the screen. + * + * @slot - Content to be displayed in the bottom sheet. Either `zeta-list-item` or `zeta-grid-menu-item`. + * @figma https://www.figma.com/file/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?node-id=21541-2225 + * @storybook https://zeta-ds.web.app/web/storybook/?path=/docs/bottom-sheet--docs + */ +@customElement("zeta-bottom-sheet") +export class ZetaBottomSheet extends LitElement { + /** Sheet Header alignment.*/ + @property({ type: String, reflect: true }) headerAlignment: "start" | "center" = "start"; + + /** Sheet Header text content.*/ + @property({ type: String, reflect: true }) headerText: string = "Title"; + + /** If the items are list items or grid items */ + @state() isGrid: boolean = false; + + /** If the content is generic. E.g. not list or grid items. */ + @state() isGenericContent: boolean = false; + + /** If the bottom sheet is collapsed or not */ + @property({ type: Boolean, reflect: true }) isExpanded: boolean = true; + + /** Default slot */ + @queryAssignedElements({ flatten: true }) items: NodeList | undefined; + + static styles = [super.styles ?? [], styles]; + + private setLayout = () => { + this.requestUpdate(); + if (this.items && this.items[0] && this.items[0] instanceof ZetaGridMenuItem) { + this.isGrid = true; + this.isGenericContent = false; + } else if (this.items && this.items[0] && this.items[0] instanceof ZetaListItem) { + this.isGrid = false; + this.isGenericContent = false; + } else { + this.isGenericContent = true; + } + }; + + protected override render() { + this.setLayout(); + return html` + <div class="container"> + <div class="handle"></div> + <div class="header">${this.headerText}</div> + <div class="content ${this.isGenericContent ? "isGenericContent" : ""}${this.isGrid ? "isGrid" : ""}" tabindex="0"> + <slot @slotchange=${this.setLayout}></slot> + </div> + </div> + `; + } +} +declare global { + interface HTMLElementTagNameMap { + "zeta-bottom-sheet": ZetaBottomSheet; + } +} diff --git a/src/components/breadcrumbs/breadcrumb-item/breadcrumb-item.styles.js b/src/components/breadcrumbs/breadcrumb-item/breadcrumb-item.styles.js new file mode 100644 index 0000000..bf067c0 --- /dev/null +++ b/src/components/breadcrumbs/breadcrumb-item/breadcrumb-item.styles.js @@ -0,0 +1,35 @@ +import { css } from "lit"; +export default css` + :host { + display: flex; + } + + a { + display: flex; + flex-wrap: nowrap; + align-items: center; + color: var(--main-subtle); + gap: var(--spacing-small); + } + + a:hover ::slotted([slot="icon"]) { + --icon-color: var(--main-primary); + } + + a:active ::slotted([slot="icon"]) { + --icon-color: var(--main-default); + } + + a:hover { + color: var(--main-primary); + } + + a:active { + color: var(--main-default); + } + + ::slotted([slot="icon"]) { + --icon-color: var(--main-subtle); + margin: var(--spacing-none) var(--spacing-minimum); + } +`; diff --git a/src/components/breadcrumbs/breadcrumb-item/breadcrumb-item.ts b/src/components/breadcrumbs/breadcrumb-item/breadcrumb-item.ts new file mode 100644 index 0000000..f266b1c --- /dev/null +++ b/src/components/breadcrumbs/breadcrumb-item/breadcrumb-item.ts @@ -0,0 +1,33 @@ +import { html, LitElement } from "lit"; +import { customElement } from "lit/decorators.js"; +import styles from "./breadcrumb-item.styles.js"; +import "../../icon/icon.js"; +import { Navigate } from "../../../mixins/mixins.js"; +import { ifDefined } from "lit/directives/if-defined.js"; + +/** + * The breadcrumb is a secondary navigation patten that helps a user understand the hierarchy among levels and navigate back through them. + * + * @figma https://www.figma.com/design/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?node-id=229-5&node-type=canvas&m=dev + * @storybook https://zeta-ds.web.app/web/storybook/index.html?path=/docs/breadcrumb--docs + */ +// TODO revisit this component +@customElement("zeta-breadcrumb-item") +export class ZetaBreadcrumbItem extends Navigate(LitElement) { + static styles = [super.styles ?? [], styles]; + + protected override render() { + return html` + <a href=${ifDefined(this.href)}> + <slot name="icon"></slot> + <span><slot></slot></span> + </a> + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + "zeta-breadcrumb-item": ZetaBreadcrumbItem; + } +} diff --git a/src/components/breadcrumbs/breadcrumb.styles.js b/src/components/breadcrumbs/breadcrumb.styles.js new file mode 100644 index 0000000..0cea985 --- /dev/null +++ b/src/components/breadcrumbs/breadcrumb.styles.js @@ -0,0 +1,55 @@ +import { css } from "lit"; +export default css` + :host([rounded]) ::slotted(zeta-breadcrumb-item:not(:first-child)):before, + :host([rounded]) zeta-breadcrumb-item:not(:first-child):before, + :host([rounded]) .more-menu:before { + font-family: "zeta-icons-round"; + } + + :host ::slotted(zeta-breadcrumb-item:not(:first-child)):before, + :host zeta-breadcrumb-item:not(:first-child):before, + .more-menu:before { + font-family: "zeta-icons-sharp"; + } + + ::slotted(zeta-breadcrumb-item:not(:first-child)):before, + .container zeta-breadcrumb-item:not(:first-child):before, + .more-menu:before { + content: "chevron_right"; + font-size: var(--spacing-large); + color: var(--main-subtle); + margin-right: var(--spacing-small); + } + + :host, + .container { + display: flex; + flex-wrap: wrap; + gap: var(--spacing-small); + } + + .more-menu { + display: flex; + align-items: center; + } + + button { + padding: var(--spacing-minimum) var(--spacing-small); + border: var(--border-size-small) solid var(--border-default); + background-color: var(--surface-warm); + cursor: pointer; + + zeta-icon { + --icon-size: var(--spacing-medium); + } + } + + button:hover { + background: var(--surface-hover); + border: var(--border-size-small) solid transparent; + } + + button:active { + background: var(--surface-selected); + } +`; diff --git a/src/components/breadcrumbs/breadcrumb.ts b/src/components/breadcrumbs/breadcrumb.ts new file mode 100644 index 0000000..74dc117 --- /dev/null +++ b/src/components/breadcrumbs/breadcrumb.ts @@ -0,0 +1,81 @@ +export * from "./breadcrumb-item/breadcrumb-item.js"; +import { html, LitElement, type TemplateResult } from "lit"; +import { customElement, property, queryAssignedElements, state } from "lit/decorators.js"; +import styles from "./breadcrumb.styles.js"; +import "../icon/icon.js"; +import { Contourable } from "../../mixins/contour.js"; +import { Interactive } from "../../mixins/interactive.js"; + +// TODO(UX-1336): Breadcrumb arrows are not aligned. +/** + * The breadcrumb is a secondary navigation patten that helps a user understand the hierarchy among levels and navigate back through them. + * + * @figma https://www.figma.com/design/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?node-id=229-5&node-type=canvas&m=dev + * @storybook https://zeta-ds.web.app/web/storybook/index.html?path=/docs/breadcrumb--docs + */ +@customElement("zeta-breadcrumb") +export class ZetaBreadcrumb extends Contourable(Interactive(LitElement)) { + static styles = [super.styles ?? [], styles]; + + /** + * The maximum number of items to display in the breadcrumb. + * For example, if the value is 3 and there are 5 items in the breadcrumb: + * * The first item will always be displayed. + * * The last 2 items will always be displayed. + * * The rest of the items will be truncated and displayed in a more menu. + */ + @property({ type: Number }) maxItems: number = 0; + + @state() itemsTruncated: (TemplateResult | HTMLElement)[] = []; + + @queryAssignedElements() items!: HTMLElement[]; + + private handleClick = () => { + if (this.maxItems == 1) { + this.itemsTruncated = [...this.itemsTruncated.slice(0, 1), ...this.items]; + } else { + this.itemsTruncated = [...this.itemsTruncated.slice(0, 1), ...this.items, ...this.itemsTruncated.slice(-(this.maxItems - 1))]; + } + }; + + private handleSlotChange = () => { + // Only truncate items if maxItems is set + if (this.maxItems != undefined && this.maxItems >= 1 && this.itemsTruncated.length == 0) { + // Minimum number of items to display is 2 + if (this.maxItems == 1) this.maxItems = 2; + + const moreMenu = + this.maxItems < this.items.length + ? html`<div class="more-menu"> + <button class="contourable-target" @click="${this.handleClick}"><zeta-icon>more_horizontal</zeta-icon></button> + </div>` + : html``; + + const leadingItem: HTMLElement[] = []; + const trailingItems: HTMLElement[] = []; + + this.items.map((item, index) => { + if (index == 0) leadingItem.push(item); + if (index > this.items.length - this.maxItems) trailingItems.push(item); + }); + + this.itemsTruncated = [...leadingItem, moreMenu, ...trailingItems]; + + const slot = this.shadowRoot?.querySelector("slot"); + slot!.style.display = "none"; + } + }; + + protected override render() { + return html` + <slot @slotchange=${this.handleSlotChange}></slot> + <div class="container">${this.itemsTruncated}</div> + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + "zeta-breadcrumb": ZetaBreadcrumb; + } +} diff --git a/src/components/button-group/button-group-item/button-group-item.styles.js b/src/components/button-group/button-group-item/button-group-item.styles.js new file mode 100644 index 0000000..c8b8977 --- /dev/null +++ b/src/components/button-group/button-group-item/button-group-item.styles.js @@ -0,0 +1,62 @@ +import { css } from "lit"; +export default css` + :host { + display: flex; + height: fit-content; + width: fit-content; + background-color: var(--surface-default); + } + :host .text { + font: var(--label-medium); + cursor: pointer; + } + + :host > button { + color: var(--main-default); + background-color: var(--surface-default); + border: var(--border-size-small) solid var(--border-subtle); + border-right: var(--group-item-border-right, var(--border-size-small) solid var(--border-subtle)); + display: flex; + flex-direction: row; + align-items: center; + font: var(--label-medium); + padding: var(--item-padding, 0); + } + + label { + display: flex; + } + + :host button > label.pad, + button { + gap: var(--spacing-minimum); + } + + :host([rounded]) > button { + border-top-left-radius: var(--group-item-left-radius, var(--radius-minimal)); + border-bottom-left-radius: var(--group-item-left-radius, var(--radius-minimal)); + border-top-right-radius: var(--group-item-right-radius, var(--radius-minimal)); + border-bottom-right-radius: var(--group-item-right-radius, var(--radius-minimal)); + } + + :host([size="medium"]) { + --item-padding: var(--group-item-padding, var(--spacing-medium)); + height: var(--spacing-7xl); + } + + :host([size="large"]) { + --item-padding: var(--group-item-padding, var(--spacing-large)); + height: 52px; /*TODO: 52px is not tokenized?*/ + } + + :host([iconName=""]) > button > .icon, + :host(:not([iconName])) > button > .icon { + display: none; + } + :host([disabled]) label { + color: var(--main-disabled); + } + :host zeta-icon { + --icon-size: 20px; + } +`; diff --git a/src/components/button-group/button-group-item/button-group-item.ts b/src/components/button-group/button-group-item/button-group-item.ts new file mode 100644 index 0000000..d274017 --- /dev/null +++ b/src/components/button-group/button-group-item/button-group-item.ts @@ -0,0 +1,77 @@ +import { LitElement, html, nothing } from "lit"; +import styles from "./button-group-item.styles.js"; +import { customElement, property, query, queryAssignedElements } from "lit/decorators.js"; +import { Contourable, Interactive } from "../../../mixins/mixins.js"; +import { ifDefined } from "lit/directives/if-defined.js"; +import "../../icon/icon.js"; + +// TODO(UX-1041): Add inverse +// TODO(UX-1337): Corner radius is not correct on buttons in the middle of a group +/** + * Button which is used by button groups. + * + * @slot - Button label content. + * @slot {zeta-icon} icon - Icon to display on leading side of button. Full list of icons can be found at https://zeta-icons.web.app/ + * + * @figma https://www.figma.com/design/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?node-id=229-45&node-type=canvas&m=dev + * @storybook https://zeta-ds.web.app/web/storybook/index.html?path=/docs/button-group--docs + */ +@customElement("zeta-button-group-item") +export class ZetaButtonGroupItem extends Contourable(Interactive(LitElement)) { + static override shadowRootOptions: ShadowRootInit = { + ...LitElement.shadowRootOptions, + mode: "open", + delegatesFocus: false + }; + + /** Name for the button, used if the button is in a form.*/ + //TODO: Does this even work in a form? + @property({ type: String }) name?: string; + + /** Size of button. */ + @property({ type: String, reflect: true }) size: "medium" | "large" = "medium"; + + /** Whether to show the dropdown icon. */ + @property({ type: Boolean, reflect: true }) showDropdown?: boolean = false; + + @property() override onclick: ((this: GlobalEventHandlers, ev: MouseEvent) => unknown) | null = null; + + @query("button") private readonly buttonElement!: HTMLElement | null; + + @queryAssignedElements({ slot: "icon", flatten: true }) icon?: Array<Node>; + private addGap = true; + + override focus() { + this.buttonElement?.focus(); + } + + override blur() { + this.buttonElement?.blur(); + } + + protected override render() { + return html` + <button ?disabled=${this.disabled} name=${ifDefined(this.name)}> + <slot name="icon"></slot> + <label class="text ${this.addGap ? "pad" : ""}"> + <slot + @slotchange=${() => { + this.addGap = this.textContent?.trim() !== ""; + this.requestUpdate(); + }} + > + </slot> + </label> + ${this.showDropdown ? html`<zeta-icon .rounded=${this.rounded}> expand_more</zeta-icon>` : nothing} + </button> + `; + } + + static styles = [super.styles ?? [], styles]; +} + +declare global { + interface HTMLElementTagNameMap { + "zeta-button-group-item": ZetaButtonGroupItem; + } +} diff --git a/src/components/button-group/button-group.styles.js b/src/components/button-group/button-group.styles.js new file mode 100644 index 0000000..fa8b993 --- /dev/null +++ b/src/components/button-group/button-group.styles.js @@ -0,0 +1,41 @@ +import { css } from "lit"; +export default css` + :host { + display: flex; + height: fit-content; + } + + /* Only display group items */ + ::slotted(*:not(zeta-button-group-item)) { + display: none; + } + + /* Handle sizes for group buttons */ + :host([size="medium"]) { + --group-item-padding: var(--spacing-medium); + } + + :host([size="large"]) { + --group-item-padding: var(--spacing-large); + } + + :host([rounded]) { + /* Round start of first button */ + ::slotted(zeta-button-group-item:first-child) { + --group-item-left-radius: var(--radius-minimal); + } + + /* Round end of last button */ + ::slotted(zeta-button-group-item:last-child) { + --group-item-right-radius: var(--radius-minimal); + } + } + /* Remove double border between buttons */ + ::slotted(zeta-button-group-item:not(*:last-child)) { + --group-item-border-right: 0; + } + :host { + --group-item-right-radius: 0; + --group-item-left-radius: 0; + } +`; diff --git a/src/components/button-group/button-group.ts b/src/components/button-group/button-group.ts new file mode 100644 index 0000000..e6d8b2a --- /dev/null +++ b/src/components/button-group/button-group.ts @@ -0,0 +1,33 @@ +import { LitElement, html } from "lit"; +import styles from "./button-group.styles.js"; +import { customElement, property } from "lit/decorators.js"; +import { Contourable } from "../../mixins/mixins.js"; + +export * from "./button-group-item/button-group-item.js"; + +/** + * Takes in Zeta Button Group Items as children and groups them by applying styling to them. + * + * Does not render any other types of children. + * + * @slot - Children must be of type `zeta-group-button` otherwise they will not be displayed. + * @figma https://www.figma.com/design/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?node-id=229-45&node-type=canvas&m=dev + * @storybook https://zeta-ds.web.app/web/storybook/index.html?path=/docs/button-group--docs + */ +@customElement("zeta-button-group") +export class ZetaButtonGroup extends Contourable(LitElement) { + /** Size of button. */ + @property({ type: String, reflect: true }) size: "medium" | "large" = "medium"; + + protected override render() { + return html`<slot></slot>`; + } + + static styles = [super.styles ?? [], styles]; +} + +declare global { + interface HTMLElementTagNameMap { + "zeta-button-group": ZetaButtonGroup; + } +} diff --git a/src/components/button/base-button.styles.js b/src/components/button/base-button.styles.js new file mode 100644 index 0000000..5602ce9 --- /dev/null +++ b/src/components/button/base-button.styles.js @@ -0,0 +1,35 @@ +import { css } from "lit"; +export default css` + :host { + display: flex; + height: fit-content; + width: fit-content; + } + :host > button { + display: flex; + align-items: center; + border: none; + justify-content: center; + overflow-x: ellipsis; + width: 100%; + font: var(--label-large); + gap: var(--spacing-small); + } + + :host([size="large"]) > button { + padding: var(--spacing-medium) var(--spacing-2xl); + --icon-size: 24px; + } + + :host > button, + :host([size="medium"]) > button { + padding: var(--spacing-small) var(--spacing-medium); + --icon-size: 24px; + } + + :host([size="small"]) > button { + padding: var(--spacing-minimum) var(--spacing-small); + font: var(--label-small); + --icon-size: 20px; + } +`; diff --git a/src/components/button/base-button.ts b/src/components/button/base-button.ts new file mode 100644 index 0000000..f9461c7 --- /dev/null +++ b/src/components/button/base-button.ts @@ -0,0 +1,58 @@ +import { query, state } from "lit/decorators.js"; +import { property } from "lit/decorators.js"; +import { Contourable, Interactive, Size } from "../../mixins/mixins.js"; +import { LitElement } from "lit"; +import styles from "./base-button.styles.js"; + +export class BaseButton extends Size(Contourable(Interactive(LitElement))) { + static formAssociated = true; + /** @internal */ + static override shadowRootOptions: ShadowRootInit = { + ...LitElement.shadowRootOptions, + mode: "open", + delegatesFocus: true + }; + + /** The type of the button when used in a form */ + @property({ type: String }) type?: "submit" | "reset" | "button"; + constructor() { + super(); + this.internals = this.attachInternals(); + } + @state() protected internals: ElementInternals; + + /** Name for the button, used if the button is in a form. */ + //TODO: Does this even work in a form? + @property({ type: String }) name?: string; + + /** The value of the name property When submitted as part of a form */ + @property({ type: String }) value?: string; + + override focus() { + this.buttonElement?.focus(); + } + + override blur() { + this.buttonElement?.blur(); + } + + override click() { + if (!this.disabled) this.buttonElement?.click(); + } + + //TODO do i need to change the target of on click events? + protected _handleClick(event: Event) { + if (this.type === "submit") { + const form = this.internals.form as HTMLFormElement; + form?.dispatchEvent(new Event("submit")); + } else if (this.type === "reset") { + const form = this.internals.form as HTMLFormElement; + form?.reset(); + } + return event; + } + /** @internal */ + @query("button") private readonly buttonElement!: HTMLElement; + + static styles = [super.styles ?? [], styles]; +} diff --git a/src/components/button/button.styles.js b/src/components/button/button.styles.js new file mode 100644 index 0000000..8ccc002 --- /dev/null +++ b/src/components/button/button.styles.js @@ -0,0 +1,24 @@ +import { css } from "lit"; +export default css` + :host([disabled]) > button > zeta-icon, + :host([disabled]) ::slotted(zeta-icon) { + --icon-color: var(--icon-button-icon-color-disabled, var(--main-disabled)); + } + + :host([flavor]:not([disabled])) > button > zeta-icon, + :host([flavor]:not([disabled])) ::slotted(zeta-icon) { + --icon-color: var(--icon-button-icon-color, var(--main-inverse)); + } + + :host([flavor="outline"]:not([disabled])) > button > zeta-icon, + :host([flavor="text"]:not([disabled])) > button > zeta-icon, + :host([flavor="outline"]:not([disabled])) ::slotted(zeta-icon), + :host([flavor="text"]:not([disabled])) ::slotted(zeta-icon) { + --icon-color: var(--icon-button-icon-color, var(--main-primary)); + } + + :host([flavor="outline-subtle"]:not([disabled])) > button > zeta-icon, + :host([flavor="outline-subtle"]:not([disabled])) ::slotted(zeta-icon) { + --icon-color: var(--icon-button-icon-color, var(--main-default)); + } +`; diff --git a/src/components/button/button.ts b/src/components/button/button.ts new file mode 100644 index 0000000..f447ecc --- /dev/null +++ b/src/components/button/button.ts @@ -0,0 +1,70 @@ +/* eslint-disable @typescript-eslint/unbound-method */ +import { html, nothing } from "lit"; +import { customElement, property, queryAssignedElements } from "lit/decorators.js"; +import { BaseButton } from "./base-button.js"; +import { ifDefined } from "lit/directives/if-defined.js"; +import { styleMap } from "lit/directives/style-map.js"; +import "../icon/icon.js"; +import { Flavored, type Flavor } from "../../mixins/flavor.js"; +import styles from "./button.styles.js"; + +//TODO text overflow broken + +export type ButtonFlavor = Exclude<Flavor, "inverse">; + +/** + * Buttons facilitate user interaction. + * + * @slot - Content shown on button; typically text. + * @slot {zeta-icon} leadingIcon - Leading icon of button. Full list of icons can be found at {@link https://zeta-icons.web.app/ | Zeta Icons}. + * @slot {zeta-icon} trailingIcon - Trailing icon of button. Full list of icons can be found at {@link https://zeta-icons.web.app/ | Zeta Icons} + * + * @figma https://www.figma.com/file/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?node-id=23126-110945 + * @storybook https://zeta-ds.web.app/web/storybook/?path=/docs/buttons--docs + */ +@customElement("zeta-button") +export class ZetaButton extends Flavored(BaseButton) { + /** @internal */ + protected _buttonType: "text" | "icon" = "text"; + + static get styles() { + return [super.styles ?? [], styles]; + } + + @queryAssignedElements({ slot: "leadingIcon", flatten: true }) leading?: Array<Node>; + @queryAssignedElements({ slot: "trailingIcon", flatten: true }) trailing?: Array<Node>; + + @property({ type: String, reflect: true }) flavor: ButtonFlavor = "primary"; + + protected render() { + const hide = styleMap({ display: "none" }); + const leadingIcon = html`<span style="${this.leading && this.leading.length > 0 ? nothing : hide}"> + <slot name="leadingIcon" @slotchange=${() => this.requestUpdate()}></slot> + </span>`; + const trailingIcon = html`<span style="${this.trailing && this.trailing.length > 0 ? nothing : hide}"> + <slot name="trailingIcon" @slotchange=${() => this.requestUpdate()}></slot> + </span>`; + return html` + <button + ?disabled=${this.disabled} + value=${ifDefined(this.value)} + name=${ifDefined(this.name)} + type=${ifDefined(this.type)} + aria-label=${ifDefined(ifDefined(this.ariaLabel))} + @click=${this._handleClick} + > + ${this._buttonType === "icon" + ? html`<zeta-icon .rounded=${this.rounded}><slot></slot></zeta-icon>` + : html`${leadingIcon} + <slot></slot> + ${trailingIcon}`} + </button> + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + "zeta-button": ZetaButton; + } +} diff --git a/src/components/button/icon-button/icon-button.styles.js b/src/components/button/icon-button/icon-button.styles.js new file mode 100644 index 0000000..fc75cf4 --- /dev/null +++ b/src/components/button/icon-button/icon-button.styles.js @@ -0,0 +1,28 @@ +import { css } from "lit"; +export default css` + :host { + height: fit-content; + width: fit-content; + } + + :host > button { + border: none; + } + + :host([size="large"]) > button { + padding: var(--spacing-medium); + } + + :host > button, + :host([size="medium"]) > button { + padding: var(--spacing-small); + } + + :host([size="small"]) > button { + padding: var(--spacing-minimum); + } + + :host([flavor]:not([disabled]):not(:hover):not(:active)) > button { + --flavor-background-color: var(--icon-button-color); + } +`; diff --git a/src/components/button/icon-button/icon-button.ts b/src/components/button/icon-button/icon-button.ts new file mode 100644 index 0000000..f2d59a8 --- /dev/null +++ b/src/components/button/icon-button/icon-button.ts @@ -0,0 +1,28 @@ +import { customElement } from "lit/decorators.js"; +import styles from "./icon-button.styles.js"; +import { ZetaButton } from "../button.js"; +import "../../icon/icon.js"; +// TODO slot icon name instead of passing it through a property +/** ZetaIconButton web component. + * + * A button containing a Zeta Icon. + * @cssproperty --icon-button-color the color of the button. + * @cssproperty --icon-button-icon-color the color of the icon. + * @cssproperty --icon-button-icon-color-disabled the color of the icon when the button is disabled. + * @slot {ZetaIconName} - The name of the icon. Full list of icons can be found at {@link https://zeta-icons.web.app/ Zeta Icons}. + * @figma https://www.figma.com/file/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?node-id=23126-110314 + * @storybook https://zeta-ds.web.app/web/storybook/?path=/docs/buttons--docs + */ +@customElement("zeta-icon-button") +export class ZetaIconButton extends ZetaButton { + static get styles() { + return [super.styles || [], styles]; + } + protected override _buttonType: "text" | "icon" = "icon"; +} + +declare global { + interface HTMLElementTagNameMap { + "zeta-icon-button": ZetaIconButton; + } +} diff --git a/src/components/card/card-body/card-body.styles.js b/src/components/card/card-body/card-body.styles.js new file mode 100644 index 0000000..d9105fa --- /dev/null +++ b/src/components/card/card-body/card-body.styles.js @@ -0,0 +1,8 @@ +import { css } from "lit"; +export default css` + .card-body { + padding: var(--spacing-medium) var(--spacing-large); + color: var(--main-subtle); + font: var(--body-small); + } +`; diff --git a/src/components/card/card-body/card-body.ts b/src/components/card/card-body/card-body.ts new file mode 100644 index 0000000..2868e41 --- /dev/null +++ b/src/components/card/card-body/card-body.ts @@ -0,0 +1,23 @@ +import { LitElement, html } from "lit"; +import { customElement } from "lit/decorators.js"; +import styles from "./card-body.styles.js"; + +/** + * Used to display the text in a card body. + * + * @slot - The content of the card body. + */ +@customElement("zeta-card-body") +export class ZetaCardBody extends LitElement { + protected render() { + return html` <div class="card-body"><slot></slot></div> `; + } + + static styles = [styles]; +} + +declare global { + interface HTMLElementTagNameMap { + "zeta-card-body": ZetaCardBody; + } +} diff --git a/src/components/card/card-footer/card-footer.styles.js b/src/components/card/card-footer/card-footer.styles.js new file mode 100644 index 0000000..a166929 --- /dev/null +++ b/src/components/card/card-footer/card-footer.styles.js @@ -0,0 +1,13 @@ +import { css } from "lit"; +export default css` + .card-footer { + padding: var(--spacing-large); + gap: var(--spacing-medium); + display: flex; + } + + ::slotted(zeta-button:not([flavor="text"])) { + width: max-content; + flex: 1; + } +`; diff --git a/src/components/card/card-footer/card-footer.ts b/src/components/card/card-footer/card-footer.ts new file mode 100644 index 0000000..dcbbbd6 --- /dev/null +++ b/src/components/card/card-footer/card-footer.ts @@ -0,0 +1,23 @@ +import { LitElement, html } from "lit"; +import { customElement } from "lit/decorators.js"; +import styles from "./card-footer.styles.js"; + +/** + * Used at the bottom of a card. Typically contains button elements. + * + * @slot - The content displayed in the footer. Should be one or two 'zeta-button's. + */ +@customElement("zeta-card-footer") +export class ZetaCardFooter extends LitElement { + protected render() { + return html`<div class="card-footer"><slot></slot></div>`; + } + + static styles = [styles]; +} + +declare global { + interface HTMLElementTagNameMap { + "zeta-card-footer": ZetaCardFooter; + } +} diff --git a/src/components/card/card-header/card-header.styles.js b/src/components/card/card-header/card-header.styles.js new file mode 100644 index 0000000..86d5fa8 --- /dev/null +++ b/src/components/card/card-header/card-header.styles.js @@ -0,0 +1,43 @@ +import { css } from "lit"; +export default css` + .card-header { + padding: var(--spacing-small) var(--spacing-large); + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--spacing-medium); + } + + /*This was copied from global-header.styles. This needs some better rethinking, how to default a button style if in a header?? */ + ::slotted(zeta-icon-button) { + --icon-button-icon-color: var(--main-default); + --icon-button-icon-color-disabled: var(--main-disabled); + --icon-button-color: var(--surface-default); + } + + .main-content { + display: flex; + flex-direction: column; + } + + h1 { + font: var(--title-medium); + margin: 0; + } + + h2 { + font: var(--body-small); + margin: 0; + } + + .leading { + display: flex; + gap: inherit; + align-items: center; + } + + .trailing { + display: flex; + justify-self: flex-end; + } +`; diff --git a/src/components/card/card-header/card-header.ts b/src/components/card/card-header/card-header.ts new file mode 100644 index 0000000..8d6ae47 --- /dev/null +++ b/src/components/card/card-header/card-header.ts @@ -0,0 +1,42 @@ +import { customElement, property } from "lit/decorators.js"; +import { LitElement, html } from "lit"; +import styles from "./card-header.styles.js"; + +/** + * Card headers are used at the top of cards. + * + * @slot - The headline text. + * @slot leading - Content placed before the headline + * @slot trailing - Content placed after the headline + */ +@customElement("zeta-card-header") +export class ZetaCardHeader extends LitElement { + /** The headline text. Can Also be slotted. */ + @property({ type: String }) headline?: string; + + /** The sub headline text. */ + @property({ type: String }) subHeadline?: string; + + protected render() { + return html`<div class="card-header"> + <div class="leading"> + <slot name="leading"></slot> + <div class="main-content"> + <h1>${this.headline}<slot></slot></h1> + <h2>${this.subHeadline}</h2> + </div> + </div> + <div class="trailing"> + <slot name="trailing"></slot> + </div> + </div>`; + } + + static styles = [styles]; +} + +declare global { + interface HTMLElementTagNameMap { + "zeta-card-header": ZetaCardHeader; + } +} diff --git a/src/components/card/card.styles.js b/src/components/card/card.styles.js new file mode 100644 index 0000000..47caf33 --- /dev/null +++ b/src/components/card/card.styles.js @@ -0,0 +1,6 @@ +import { css } from "lit"; +export default css` + .card { + border: var(--border-size-small) solid var(--border-default); + } +`; diff --git a/src/components/card/card.ts b/src/components/card/card.ts new file mode 100644 index 0000000..c97e7f7 --- /dev/null +++ b/src/components/card/card.ts @@ -0,0 +1,31 @@ +import { html, LitElement } from "lit"; +import { Contourable } from "../../mixins/mixins.js"; +import { customElement } from "lit/decorators.js"; +import styles from "./card.styles.js"; + +export * from "./card-body/card-body.js"; +export * from "./card-footer/card-footer.js"; +export * from "./card-header/card-header.js"; + +/** + * Cards are used to display content. + * + * @slot - The content of the card. + * + * @figma https://www.figma.com/design/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?node-id=229-10&node-type=canvas&m=dev + * @storybook https://zeta-ds.web.app/web/storybook/index.html?path=/docs/cards--docs + */ +@customElement("zeta-card") +export class ZetaCard extends Contourable(LitElement) { + protected render() { + return html` <div class="card"><slot></slot></div>`; + } + + static styles = [styles, super.styles || []]; +} + +declare global { + interface HTMLElementTagNameMap { + "zeta-card": ZetaCard; + } +} diff --git a/src/components/checkbox/checkbox.styles.js b/src/components/checkbox/checkbox.styles.js new file mode 100644 index 0000000..cedaad1 --- /dev/null +++ b/src/components/checkbox/checkbox.styles.js @@ -0,0 +1,27 @@ +import { css } from "lit"; +export default css` + :host([disabled]) *[part="icon"] { + color: var(--main-disabled); + } + + :host([indeterminate]:not([disabled])) label, + :host([checked]:not([disabled])) label { + .container { + background-color: var(--surface-primary); + } + + &:hover .container { + background-color: var(--border-hover); + } + } + + :host([rounded]) > .container { + border-radius: 2px !important; + } + + label { + cursor: pointer; + width: auto !important; + height: 100% !important; + } +`; diff --git a/src/components/checkbox/checkbox.ts b/src/components/checkbox/checkbox.ts new file mode 100644 index 0000000..d3640ac --- /dev/null +++ b/src/components/checkbox/checkbox.ts @@ -0,0 +1,33 @@ +import { customElement } from "lit/decorators.js"; +import { type InputType } from "../../mixins/form-field.js"; +import { BaseToggleFormElement } from "../base-toggle-form-element.js"; +import styles from "./checkbox.styles.js"; +import "../icon/icon.js"; + +/** + * Checkboxes allow users to select one or more items from a set. Checkboxes can turn an option on or off. + * + * @figma https://www.figma.com/file/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?node-id=21510-54003 + * @storybook https://zeta-ds.web.app/web/storybook/?path=/docs/checkbox--docs + */ +@customElement("zeta-checkbox") +export class ZetaCheckbox extends BaseToggleFormElement { + constructor() { + super(); + this.internals.role = "checkbox"; + } + + override type = "checkbox" as InputType; + override value = "on"; + + click() { + if (!this.disabled) this.input?.click(); + } + static styles = [styles, super.styles]; +} + +declare global { + interface HTMLElementTagNameMap { + "zeta-checkbox": ZetaCheckbox; + } +} diff --git a/src/components/chips/assist-chip/assist-chip.ts b/src/components/chips/assist-chip/assist-chip.ts new file mode 100644 index 0000000..2c4d3ff --- /dev/null +++ b/src/components/chips/assist-chip/assist-chip.ts @@ -0,0 +1,37 @@ +import { customElement, property } from "lit/decorators.js"; +import "../../icon/icon.js"; +import { html, nothing } from "lit"; +import type { ZetaIconName } from "@zebra-fed/zeta-icons"; +import { BaseChip } from "../base-chips/base-chip.js"; + +/** Zeta assist Chip web component. + * + * @figma https://www.figma.com/file/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?node-id=21265-14215 + * @storybook https://zeta-ds.web.app/web/storybook/?path=/docs/chips--docs + * + * @slot - The content of the chip. + */ +@customElement("zeta-assist-chip") +export class ZetaAssistChip extends BaseChip { + @property({ type: String }) icon?: ZetaIconName; + + static styles = [super.styles || []]; + + getIcon() { + if (this.icon) { + return html`<zeta-icon size="18">${this.icon}</zeta-icon>`; + } else { + return nothing; + } + } + + protected override render() { + return html` <button class="container interactive-target">${this.getIcon()}<slot></slot></button> `; + } +} + +declare global { + interface HTMLElementTagNameMap { + "zeta-assist-chip": ZetaAssistChip; + } +} diff --git a/src/components/chips/base-chips/base-chip.styles.js b/src/components/chips/base-chips/base-chip.styles.js new file mode 100644 index 0000000..5469966 --- /dev/null +++ b/src/components/chips/base-chips/base-chip.styles.js @@ -0,0 +1,36 @@ +import { css } from "lit"; +export default css` + :host([rounded]) .container { + border-radius: var(--radius-full); + } + + :host([disabled]) .container { + background-color: var(--surface-disabled); + color: var(--main-disabled); + outline-color: var(--border-disabled); + } + + .container { + height: var(--spacing-5xl); + width: fit-content; + display: flex; + align-items: center; + justify-content: center; + gap: var(--spacing-small); + padding: var(--spacing-small) var(--spacing-medium); + outline: var(--border-default) solid var(--border-size-small); + border: none; + color: var(--main-default); + transition: background-color 0.2s ease-out; + background-color: var(--surface-default); + + &:hover, + &:active { + background-color: var(--surface-hover); + } + } + + zeta-icon { + --icon-size: 20px; + } +`; diff --git a/src/components/chips/base-chips/base-chip.ts b/src/components/chips/base-chips/base-chip.ts new file mode 100644 index 0000000..c9c2b16 --- /dev/null +++ b/src/components/chips/base-chips/base-chip.ts @@ -0,0 +1,11 @@ +import { html, LitElement } from "lit"; +import { Contourable, Interactive } from "../../../mixins/mixins.js"; +import styles from "./base-chip.styles.js"; + +export class BaseChip extends Interactive(Contourable(LitElement)) { + static override styles = [styles, super.styles || []]; + + protected override render() { + return html`<div class="container"><slot></slot></div>`; + } +} diff --git a/src/components/chips/base-chips/interactive-chip.styles.js b/src/components/chips/base-chips/interactive-chip.styles.js new file mode 100644 index 0000000..5eaa2d9 --- /dev/null +++ b/src/components/chips/base-chips/interactive-chip.styles.js @@ -0,0 +1,13 @@ +import { css } from "lit"; + +export default css` + :host .container:active { + background-color: var(--surface-selected); + } + + :host([disabled]) .container { + background-color: var(--surface-disabled); + color: var(--main-disabled); + box-shadow: 0 0 0 var(--border-size-small) var(--border-disabled); + } +`; diff --git a/src/components/chips/base-chips/interactive-chip.ts b/src/components/chips/base-chips/interactive-chip.ts new file mode 100644 index 0000000..6ea044f --- /dev/null +++ b/src/components/chips/base-chips/interactive-chip.ts @@ -0,0 +1,9 @@ +import { property } from "lit/decorators.js"; +import { BaseChip } from "./base-chip"; +import styles from "./interactive-chip.styles.js"; + +export class InterativeChip extends BaseChip { + @property({ type: Boolean }) disabled: boolean = false; + + static styles = [styles, super.styles || []]; +} diff --git a/src/components/chips/chips.ts b/src/components/chips/chips.ts new file mode 100644 index 0000000..8331d26 --- /dev/null +++ b/src/components/chips/chips.ts @@ -0,0 +1,6 @@ +export * from "./assist-chip/assist-chip.js"; +export * from "./filter-chip/filter-chip.js"; +export * from "./input-chip/input-chip.js"; +export * from "./status-chip/status-chip.js"; + +// TODO(UX-1338): Refactor chips to use a single chip component and make them draggable diff --git a/src/components/chips/filter-chip/filter-chip.styles.js b/src/components/chips/filter-chip/filter-chip.styles.js new file mode 100644 index 0000000..48d034d --- /dev/null +++ b/src/components/chips/filter-chip/filter-chip.styles.js @@ -0,0 +1,17 @@ +import { css } from "lit"; +export default css` + :host([active]:not([disabled])) .container { + background: var(--surface-default-inverse) !important; + color: var(--main-inverse); + } + zeta-icon { + --icon-size: 20px; + } + :host([type="selected"]) zeta-icon { + --icon-color: var(--main-inverse); + } + + .container { + transition: background-color 0.2s ease-out; + } +`; diff --git a/src/components/chips/filter-chip/filter-chip.ts b/src/components/chips/filter-chip/filter-chip.ts new file mode 100644 index 0000000..84d0ea7 --- /dev/null +++ b/src/components/chips/filter-chip/filter-chip.ts @@ -0,0 +1,45 @@ +import { html, nothing } from "lit"; +import { customElement, property } from "lit/decorators.js"; +import styles from "./filter-chip.styles.js"; +import "../../icon/icon.js"; +import { BaseChip } from "../base-chips/base-chip.js"; + +/** Zeta Filter Chip web component. + * + * @figma https://www.figma.com/file/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?node-id=21265-14112 + * @storybook https://zeta-ds.web.app/web/storybook/?path=/docs/chips--docs + */ +@customElement("zeta-filter-chip") +export class ZetaFilterChip extends BaseChip { + @property({ type: Boolean, reflect: true }) active?: boolean; + + static styles = [super.styles ?? [], styles]; + + getIcon() { + if (this.active) { + return html`<zeta-icon color=${this.disabled ? "var(--main-disabled)" : "var(--main-inverse)"} class="icon" size="20">check_mark</zeta-icon>`; + } else { + return nothing; + } + } + + clickHanlder() { + const event = new CustomEvent("change", { + detail: this.active + }); + this.active = !this.active; + this.dispatchEvent(event); + } + + protected override render() { + return html`<button class="container interactive-target" @click=${() => this.clickHanlder()} ?disabled=${this.disabled}> + ${this.getIcon()}<slot></slot> + </button>`; + } +} + +declare global { + interface HTMLElementTagNameMap { + "zeta-filter-chip": ZetaFilterChip; + } +} diff --git a/src/components/chips/input-chip/input-chip.styles.js b/src/components/chips/input-chip/input-chip.styles.js new file mode 100644 index 0000000..53b650a --- /dev/null +++ b/src/components/chips/input-chip/input-chip.styles.js @@ -0,0 +1,6 @@ +import { css } from "lit"; +export default css` + :host .container { + height: var(--spacing-2xl); + } +`; diff --git a/src/components/chips/input-chip/input-chip.ts b/src/components/chips/input-chip/input-chip.ts new file mode 100644 index 0000000..2a5e3e5 --- /dev/null +++ b/src/components/chips/input-chip/input-chip.ts @@ -0,0 +1,29 @@ +import { html } from "lit"; +import { customElement } from "lit/decorators.js"; +import styles from "./input-chip.styles.js"; +import "../../icon/icon.js"; +import { BaseChip } from "../base-chips/base-chip.js"; + +/** Zeta Input Chip web component. + * + * @figma https://www.figma.com/file/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?node-id=21265-2159 + * @storybook https://zeta-ds.web.app/web/storybook/?path=/docs/chips--docs + */ +@customElement("zeta-input-chip") +export class ZetaInputChip extends BaseChip { + static styles = [super.styles ?? [], styles]; + + protected override render() { + return html`<div class="container"> + <slot name="leading"></slot> + <slot></slot> + <zeta-icon>close</zeta-icon> + </div>`; + } +} + +declare global { + interface HTMLElementTagNameMap { + "zeta-input-chip": ZetaInputChip; + } +} diff --git a/src/components/chips/status-chip/status-chip.styles.js b/src/components/chips/status-chip/status-chip.styles.js new file mode 100644 index 0000000..0040dfb --- /dev/null +++ b/src/components/chips/status-chip/status-chip.styles.js @@ -0,0 +1,11 @@ +import { css } from "lit"; +export default css` + .container { + width: fit-content; + display: flex; + align-items: center; + padding: var(--spacing-minimum) var(--spacing-small); + background-color: var(--surface-warm); + font: var(--body-x-small); + } +`; diff --git a/src/components/chips/status-chip/status-chip.ts b/src/components/chips/status-chip/status-chip.ts new file mode 100644 index 0000000..d1b53df --- /dev/null +++ b/src/components/chips/status-chip/status-chip.ts @@ -0,0 +1,27 @@ +import { html, LitElement } from "lit"; +import { customElement, property } from "lit/decorators.js"; +import styles from "./status-chip.styles.js"; +import { Contourable } from "../../../mixins/mixins.js"; + +/** Zeta Status Chip web component. + * + * @figma https://www.figma.com/file/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?node-id=21265-14282 + * @storybook https://zeta-ds.web.app/web/storybook/?path=/docs/chips--docs + */ +@customElement("zeta-status-chip") +export class ZetaStatusChip extends Contourable(LitElement) { + /** Text displayed in the chip. */ + @property({ type: String }) text: string = ""; + + static styles = [super.styles ?? [], styles]; + + protected override render() { + return html`<label for="" class="container">${this.text}</label>`; + } +} + +declare global { + interface HTMLElementTagNameMap { + "zeta-status-chip": ZetaStatusChip; + } +} diff --git a/src/components/dialog/dialog.styles.js b/src/components/dialog/dialog.styles.js new file mode 100644 index 0000000..c55e08c --- /dev/null +++ b/src/components/dialog/dialog.styles.js @@ -0,0 +1,117 @@ +import { css } from "lit"; +export default css` + dialog { + flex-direction: column; + width: fit-content; + width: 100%; + max-width: 480px; + max-height: 80vh; + overflow: auto; + + border: none; + border-radius: inherit; + padding: 0 var(--spacing-large); + + background-color: var(--surface-default); + color: var(--main-default); + box-shadow: var(--elevation-6); + } + + dialog[open] { + display: flex; + } + + header { + display: flex; + flex-direction: var(--_dialog-header-direction, column); + padding: var(--spacing-2xl) var(--spacing-2xl) var(--spacing-medium); + row-gap: var(--spacing-small); + column-gap: var(--spacing-large); + } + + :host([centered]) header { + align-items: center; + } + + footer { + display: flex; + flex-shrink: 0; + padding: var(--spacing-large) var(--spacing-2xl) var(--spacing-2xl) var(--spacing-2xl); + gap: var(--spacing-large); + justify-content: flex-end; + margin-top: var(--spacing-2xl); + overflow: auto; + + ::slotted(:not(zeta-button)) { + display: none; + } + + ::slotted(zeta-button) { + width: 100%; + } + + .actions { + display: flex; + gap: var(--spacing-large); + width: 100%; + } + } + + /*::slotted([slot="other"]) { + display: flex; + width: fit-content; + }*/ + + h1 { + font: var(--headline-small); + margin: 0; + padding: 0; + } + + div[part="body"] { + display: flex; + margin: var(--spacing-small) var(--spacing-2xl); + flex: 1; + min-height: 1.125rem; + overflow: auto; + } + + /*default size = small*/ + :host(:not([size="large"])) div[part="body"] { + font: var(--body-small); + min-height: 1.125rem; + } + + :host([size="large"]) div[part="body"] { + /*Note: the designs as of 28/11/2024 show a different font size for the large dialog body ("Standard/Body/Large"). This isnt in ZDS tokens and needs to be changed in Figma*/ + font: var(--body-medium); + min-height: 1.5rem; + } + + :host(:not([size="large"])) { + footer { + flex-direction: row; + } + footer[data-element-count="3"] { + flex-direction: column-reverse; + & .actions { + flex-direction: column-reverse; + } + } + } + + :host([size="large"]) { + footer[data-element-count="3"] { + justify-content: space-between; + } + + & .actions, + & ::slotted([slot="other"]) { + width: fit-content; + } + } + + zeta-icon { + --icon-size: 32px; + } +`; \ No newline at end of file diff --git a/src/components/dialog/dialog.ts b/src/components/dialog/dialog.ts new file mode 100644 index 0000000..a941a96 --- /dev/null +++ b/src/components/dialog/dialog.ts @@ -0,0 +1,139 @@ +/* eslint-disable @typescript-eslint/no-floating-promises */ +import { customElement, property, queryAssignedElements } from "lit/decorators.js"; +import { LitElement, html } from "lit"; +import styles from "./dialog.styles.js"; +import { ZetaButton } from "../button/button.js"; +import { Contourable, Popup } from "../../mixins/mixins.js"; +import "../icon/icon.js"; + +/* + * TODO: dialog Autofocus. + */ +/** + * A reusable dialog or modal window with a customizable interface and functionality. + * + * A dialog should popup either in response to user action or to get the users attention. + * @slot - Body of dialog; typically text. + * @slot {zeta-icon} icon - A `zeta-icon` element. Size will be restricted based on dialog type. + * @slot {zeta-button} confirm - Button used in footer. Must be of type zeta-button. + * @slot {zeta-button} cancel - Button used in footer. Must be of type zeta-button. + * @slot {zeta-button} other - Button used in footer. Must be of type zeta-button. + * + * @part body - Styles the dialog body + * @part footer - Styles the dialog footer + * @part header - Styles the dialog header + * + * @figma https://www.figma.com/design/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?node-id=229-14&node-type=canvas&m=dev + * @storybook https://zeta-ds.web.app/web/storybook/index.html?path=/docs/dialog--docs + */ +@customElement("zeta-dialog") +export class ZetaDialog extends Contourable(Popup(LitElement)) { + constructor() { + super(); + this.addEventListener("submit", this.handleSubmit); + } + static override shadowRootOptions = { + ...LitElement.shadowRootOptions, + delegatesFocus: true + }; + + // In case of form in the dialog body, close and set returnValue to button value + private handleSubmit = (e: SubmitEvent) => { + const form = e.target as HTMLFormElement; + const { submitter } = e; + if (form.method !== "dialog" || !submitter) { + return; + } + this.hide(submitter.getAttribute("value") ?? this.returnValue); + }; + + private _title: string = ""; + /** Title of the dialog. */ + @property({ type: String }) + get title() { + return this._title; + } + + set title(value: string) { + this._title = value; + } + + /** Whether header text should be centered. */ + @property({ type: Boolean, reflect: true }) centered: boolean = false; + + /** Whether the modal is initially open. */ + @property({ type: Boolean }) initialOpen: boolean = false; + + @property({ type: String, reflect: true }) size: "small" | "large" = "small"; + + @queryAssignedElements({ slot: "icon", flatten: true }) icon!: NodeList; + + /** Action button 1 (Confirm). */ + @queryAssignedElements({ slot: "confirm", flatten: true }) confirmBtn!: NodeList; + + /** Action button 2 (Cancel). */ + @queryAssignedElements({ slot: "cancel", flatten: true }) cancelBtn!: NodeList; + + /** Action button 3 (Learn more/Other). */ + @queryAssignedElements({ slot: "other", flatten: true }) otherBtn!: NodeList; + + // set props to buttons + private handleActionButtonChange = () => { + this.requestUpdate(); + let count = 0; + if (this.confirmBtn[0] && this.confirmBtn[0] instanceof ZetaButton) { + this.confirmBtn[0].flavor = "primary"; + this.confirmBtn[0].rounded = this.rounded; + count++; + } + if (this.cancelBtn[0] && this.cancelBtn[0] instanceof ZetaButton) { + this.cancelBtn[0].flavor = "outline-subtle"; + this.cancelBtn[0].rounded = this.rounded; + count++; + } + if (this.otherBtn[0] && this.otherBtn[0] instanceof ZetaButton) { + this.otherBtn[0].flavor = "text"; + this.otherBtn[0].rounded = this.rounded; + count++; + } + return count; + }; + + protected render() { + const count = this.handleActionButtonChange(); + + return html` + <dialog .returnValue=${this.returnValue} id=${this.id} ?open=${this.initialOpen}> + <header part="header"> + <slot name="icon"></slot> + <h1>${this._title}</h1> + </header> + <div part="body"><slot></slot></div> + <footer data-element-count=${count} part="footer"> + <slot @click=${() => this.hide("other")} @slotchange=${this.handleActionButtonChange} name="other"></slot> + <div class="actions"> + <slot @click=${() => this.hide("cancel")} @slotchange=${this.handleActionButtonChange} name="cancel"></slot> + <slot + @click=${(e: Event) => { + const btn = e.target as HTMLButtonElement; + if (btn.type !== "submit") { + this.hide("confirm"); + } + }} + @slotchange=${this.handleActionButtonChange} + name="confirm" + ></slot> + </div> + </footer> + </dialog> + `; + } + + static styles = [styles, super.styles || []]; +} + +declare global { + interface HTMLElementTagNameMap { + "zeta-dialog": ZetaDialog; + } +} \ No newline at end of file diff --git a/src/components/dropdown/dropdown-menu/dropdown-menu-button.styles.js b/src/components/dropdown/dropdown-menu/dropdown-menu-button.styles.js new file mode 100644 index 0000000..543e789 --- /dev/null +++ b/src/components/dropdown/dropdown-menu/dropdown-menu-button.styles.js @@ -0,0 +1,24 @@ +import { css } from "lit"; +export default css` + :host { + display: block; + width: fit-content; + } + + .droppable-item { + height: var(--spacing-6xl); + padding-left: var(--spacing-medium); + } + + .droppable-item:hover { + background-color: var(--surface-hover); + } + + .droppable-item:active { + background-color: var(--surface-selected); + } + + :host([rounded]) .droppable-item { + border-radius: var(--radius-minimal); + } +`; diff --git a/src/components/dropdown/dropdown-menu/dropdown-menu-button.ts b/src/components/dropdown/dropdown-menu/dropdown-menu-button.ts new file mode 100644 index 0000000..6d3b497 --- /dev/null +++ b/src/components/dropdown/dropdown-menu/dropdown-menu-button.ts @@ -0,0 +1,188 @@ +import { customElement, property, query, state } from "lit/decorators.js"; +import styles from "./dropdown-menu-button.styles.js"; +import { html, LitElement } from "lit"; +import { Contourable, Flavored, Size } from "../../../mixins/mixins.js"; +import { FormField, type InputType } from "../../../mixins/form-field.js"; +import type { ZetaDroppable } from "../droppable.js"; +import "../../button/button.js"; +import "../../radio-button/radio-button.js"; +import "../../checkbox/checkbox.js"; +import "../menu-item/dropdown-menu-item.js"; +import "./../droppable"; +import type { ZetaIconName } from "@zebra-fed/zeta-icons"; + +export type ZetaDropdownItem = { label: string; icon?: ZetaIconName; checked?: boolean; disabled?: boolean; onClick?: () => void }; + +/** Zeta Dropdown Menu Button places a button that when clicked opens a dropdown menu containing the items passed into it through the items prop. + * + * @slot - The slotted text will be displayed on the dropdown menu button. + * @figma https://www.figma.com/file/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?node-id=22391-10146 + * @storybook https://zeta-ds.web.app/web/storybook/?path=/docs/dropdown--docs + */ +@customElement("zeta-dropdown-menu-button") +export class ZetaDropdownMenuButton extends FormField(Contourable(Flavored(Size(LitElement)))) { + /** Controls the state of the dropdown menu. */ + @property({ type: Boolean }) open: boolean = false; + + // TODO(UX-1024): Investigate moving items into the slot + /** Array of items to populate the dropdown */ + @property({ type: Array }) items: Array<ZetaDropdownItem> = [ + { + label: "Auto Item", + icon: "star", + checked: false + } + ]; + + /** The type of dropdown. + * - "text-dropdown" - A dropdown with a default dropdown buttons. + * - "checkbox-dropdown" - A dropdown with checkboxes. + * - "radio-dropdown" - A dropdown with radio buttons. + */ + @property({ type: String }) type: InputType = "text-dropdown"; + + /** The name of the dropdown menu button for form control*/ + @property({ type: String }) name: string = "default"; + + /** The direction of the droppable relative to the anchor. Defaults to bottom if left undefined.*/ + @property({ type: String }) direction?: "left" | "right" | "bottom" | "top" = "bottom"; + + @state() icon: string = "chevron_left"; + + private checkedValues: string[] = []; + + @query("#anchor") anchor!: HTMLElement; + + @query("zeta-droppable") droppable!: ZetaDroppable; + + override handleChange(event: Event): void { + this.dispatchEvent(new Event(event.type, event)); + } + + protected firstUpdated() { + this.droppable.anchor = this.anchor; + this.items.map(item => { + if (item.checked) { + if (this.type === "checkbox-dropdown") { + this.checkedValues = [...this.checkedValues, item.label]; + this.input.value = this.checkedValues.toString(); + } else if (this.type === "radio-dropdown") { + this.input.value = item.label; + } + this.input.dispatchEvent(new Event("input")); + } + }); + } + + private handleItemClick(text: string) { + if (this.type === "text-dropdown") { + this.input.value = text; + this.handleClick(); + } else if (this.type === "checkbox-dropdown") { + if (this.checkedValues.includes(text)) { + this.checkedValues = this.checkedValues.filter((item: string) => item !== text); + } else { + this.checkedValues = [...this.checkedValues, text]; + } + this.input.value = this.checkedValues.toString(); + } else if (this.type === "radio-dropdown") { + this.input.value = text; + } + this.input.dispatchEvent(new Event("input")); + } + + private handleClick() { + this.open = !this.open; + if (this.open) { + document.body.style.overflow = "hidden"; + this.icon = "expand_more"; + } else { + document.body.style.overflow = "auto"; + this.icon = "chevron_left"; + } + } + + private handleOutsideClick(e: Event) { + if (this.open && !this.contains(e.target as Node)) { + this.handleClick(); + } + } + + private renderItems() { + if (this.type === "radio-dropdown") { + return this.items.map(item => { + return html`<zeta-radio-button + @change=${() => { + this.handleItemClick(item.label); + if (item.onClick) { + item.onClick(); + } + }} + class="droppable-item" + name=${this.name} + ?checked=${item.checked} + >${item.label}</zeta-radio-button + >`; + }); + } else if (this.type === "checkbox-dropdown") { + return this.items.map(item => { + return html` + <zeta-checkbox + @change=${() => { + this.handleItemClick(item.label); + if (item.onClick) { + item.onClick(); + } + }} + class="droppable-item" + name=${this.name} + ?rounded=${this.rounded} + ?checked=${item.checked} + >${item.label}</zeta-checkbox + > + `; + }); + } else { + return this.items.map(item => { + return html`<zeta-dropdown-menu-item + @click=${() => { + this.handleItemClick(item.label); + if (item.onClick) { + item.onClick(); + } + }} + ?rounded=${this.rounded} + ><zeta-icon slot="icon" ?rounded=${this.rounded}>${item.icon}</zeta-icon>${item.label}</zeta-dropdown-menu-item + >`; + }); + } + } + + protected render() { + document.addEventListener("click", this.handleOutsideClick.bind(this)); + return html` + <zeta-button + id="anchor" + @click=${() => { + this.handleClick(); + }} + .size=${this.size} + ?rounded=${this.rounded} + .flavor=${this.flavor} + ><slot></slot><zeta-icon .rounded=${this.rounded}>${this.icon}</zeta-icon></zeta-button + > + <zeta-droppable .anchor=${this.anchor} .direction=${this.direction} .matchParentWidth=${true} ?open=${this.open} ?rounded=${this.rounded} + >${this.renderItems()}</zeta-droppable + > + ${super.render()} + `; + } + + static styles = [styles, super.styles || []]; +} + +declare global { + interface HTMLElementTagNameMap { + "zeta-dropdown-menu-button": ZetaDropdownMenuButton; + } +} diff --git a/src/components/dropdown/droppable.styles.js b/src/components/dropdown/droppable.styles.js new file mode 100644 index 0000000..c70ce2d --- /dev/null +++ b/src/components/dropdown/droppable.styles.js @@ -0,0 +1,21 @@ +import { css } from "lit"; +export default css` + :host { + display: none; + position: absolute; + } + + :host([open]) { + display: flex; + flex-direction: column; + background-color: var(--surface-default); + gap: var(--spacing-minimum); + padding: var(--spacing-medium); + box-shadow: var(--elevation-3); + z-index: 5; + } + + :host([rounded]) { + border-radius: var(--radius-minimal); + } +`; diff --git a/src/components/dropdown/droppable.ts b/src/components/dropdown/droppable.ts new file mode 100644 index 0000000..d9bdca7 --- /dev/null +++ b/src/components/dropdown/droppable.ts @@ -0,0 +1,155 @@ +export * from "./menu-item/dropdown-menu-item.js"; +import { customElement, property, queryAssignedElements } from "lit/decorators.js"; +import styles from "./droppable.styles.js"; +import { html, LitElement, nothing } from "lit"; +import { Contourable } from "../../mixins/mixins.js"; + +/** Zeta Droppable is a container that can be opened and closed and can be attached to an anchor which will determine it's position. + * + * @figma https://www.figma.com/file/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?node-id=22391-10146 + * @storybook https://zeta-ds.web.app/web/storybook/?path=/docs/dropdown--docs + */ +@customElement("zeta-droppable") +export class ZetaDroppable extends Contourable(LitElement) { + /** Controls the open state of the droppable. */ + @property({ type: Boolean, reflect: true }) open: boolean = false; + + /** Controls whether the droppable is fit to the width of the anchor */ + @property({ type: Boolean }) matchParentWidth: boolean = false; + + /** The anchor element to position the droppable */ + @property({ type: Object }) anchor?: HTMLElement; + + /** The alignment of the droppable relative to the anchor */ + @property({ type: String }) alignment?: "start" | "end" | "center"; + + /** The direction of the droppable relative to the anchor */ + @property({ type: String }) direction?: "left" | "right" | "bottom" | "top" = "bottom"; + + @queryAssignedElements() slottedElements!: Array<HTMLElement>; + + private getAnchorPosition() { + if (this.anchor) { + const anchorRect = this.anchor.getBoundingClientRect(); + return { + top: anchorRect.top + window.scrollY, + left: anchorRect.left + window.scrollX, + right: anchorRect.right + window.scrollX, + width: anchorRect.width, + height: anchorRect.height + }; + } + return { top: 0, left: 0, right: 0, width: 0, height: 0 }; + } + + private getDroppablePosition() { + const rect = this.getBoundingClientRect(); + return { + top: rect.top + window.scrollY, + left: rect.left + window.scrollX, + width: rect.width, + height: rect.height + }; + } + + private topVisible = (e: HTMLElement) => { + return e.getBoundingClientRect().top >= 0; + }; + + private bottomVisible = (e: HTMLElement) => { + return e.getBoundingClientRect().bottom <= (window.innerHeight || document.documentElement.clientHeight); + }; + + private alignHorizontally = () => { + const anchorPosition = this.getAnchorPosition(); + const droppablePosition = this.getDroppablePosition(); + + if (this.alignment == "end") { + this.style.left = "0"; + this.style.left = `${anchorPosition.right - droppablePosition.width}px`; + } else if (this.alignment == "center") { + this.style.left = `${anchorPosition.left + anchorPosition.width / 2 - droppablePosition.width / 2}px`; + } else { + this.style.left = `${anchorPosition.left}px`; + } + }; + + private alignVertically = () => { + const anchorPosition = this.getAnchorPosition(); + const droppablePosition = this.getDroppablePosition(); + if (this.alignment == "end") { + this.style.top = `${anchorPosition.top - droppablePosition.height + anchorPosition.height}px`; + } else if (this.alignment == "center") { + this.style.top = `${anchorPosition.top + anchorPosition.height / 2 - droppablePosition.height / 2}px`; + } + }; + + private setDroppablePosition = () => { + const anchorPosition = this.getAnchorPosition(); + const droppablePosition = this.getDroppablePosition(); + + if (this.anchor) { + switch (this.direction) { + case "left": + this.style.top = `${anchorPosition.top}px`; + this.style.left = `${anchorPosition.left - droppablePosition.width}px`; + this.alignVertically(); + break; + case "right": + this.style.top = `${anchorPosition.top}px`; + this.style.left = `${anchorPosition.left + anchorPosition.width}px`; + this.alignVertically(); + break; + case "top": + this.style.top = `${anchorPosition.top - droppablePosition.height}px`; + this.alignHorizontally(); + break; + case "bottom": + this.style.top = `${anchorPosition.top + anchorPosition.height}px`; + this.alignHorizontally(); + break; + default: + this.style.top = `${anchorPosition.top + anchorPosition.height}px`; + if (!this.topVisible(this)) { + this.style.top = `${anchorPosition.top + anchorPosition.height}px`; + } + if (!this.bottomVisible(this)) { + this.style.top = `${anchorPosition.top - droppablePosition.height}px`; + } + this.alignHorizontally(); + break; + } + } + }; + + protected updated() { + if (this.open) { + this.setDroppablePosition(); + } + + const anchorPosition = this.getAnchorPosition(); + + if (this.anchor) { + this.style.top = `${anchorPosition.top + anchorPosition.height}px`; + } + if (this.matchParentWidth) { + const style = window.getComputedStyle(this); + this.style.width = `calc(${anchorPosition.width}px - ${style.paddingLeft} - ${style.paddingRight})`; + } + } + + protected render() { + if (this.open) { + return html` <slot></slot> `; + } + return nothing; + } + + static styles = [styles, super.styles || []]; +} + +declare global { + interface HTMLElementTagNameMap { + "zeta-droppable": ZetaDroppable; + } +} diff --git a/src/components/dropdown/menu-item/dropdown-menu-item.styles.js b/src/components/dropdown/menu-item/dropdown-menu-item.styles.js new file mode 100644 index 0000000..3a8cc3e --- /dev/null +++ b/src/components/dropdown/menu-item/dropdown-menu-item.styles.js @@ -0,0 +1,45 @@ +import { css } from "lit"; +export default css` + .header { + display: flex; + flex: 1; + } + + .leading { + display: flex; + } + + .droppable-item { + background-color: var(--surface-default); + padding: var(--spacing-small) var(--spacing-medium); + display: flex; + align-items: center; + gap: var(--spacing-small); + user-select: none; + font: var(--body-medium); + } + + .droppable-item:hover { + background-color: var(--surface-hover); + } + + .droppable-item:active { + background-color: var(--surface-selected); + } + + :host([disabled]) .droppable-item { + background-color: var(--surface-disabled); + color: var(--main-disabled); + } + + :host ::slotted(zeta-icon) { + --icon-color: var(--main-subtle); + } + :host([disabled]) ::slotted(zeta-icon) { + --icon-color: var(--main-disabled); + } + + ::slotted(zeta-icon) { + --icon-size: 20px; + } +`; diff --git a/src/components/dropdown/menu-item/dropdown-menu-item.ts b/src/components/dropdown/menu-item/dropdown-menu-item.ts new file mode 100644 index 0000000..e7a7f19 --- /dev/null +++ b/src/components/dropdown/menu-item/dropdown-menu-item.ts @@ -0,0 +1,45 @@ +import { customElement } from "lit/decorators.js"; +import styles from "./dropdown-menu-item.styles.js"; +import { html, LitElement } from "lit"; +import { Contourable, Interactive } from "../../../mixins/mixins.js"; +import "../../icon/icon.js"; + +/** Zeta Dropdown Menu Item is a component that represents an item in a dropdown menu. + * + * @slot - Content of menu item; typically text. + * @slot {zeta-icon} icon - A `zeta-icon` element shown on leading side of item. Only shown if `type` is `default`. + * @figma https://www.figma.com/file/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?node-id=22391-10146 + * @storybook https://zeta-ds.web.app/web/storybook/?path=/docs/dropdown--docs + */ +@customElement("zeta-dropdown-menu-item") +export class ZetaDropdownMenuItem extends Contourable(Interactive(LitElement)) { + key(e: KeyboardEvent, type: "down" | "up") { + if (type === "up") { + if (e.key === " ") { + this.click(); + } + } + } + + protected render() { + return html`<div + class="droppable-item" + tabindex=${0} + @keydown=${(e: KeyboardEvent) => this.key(e, "down")} + @keyup=${(e: KeyboardEvent) => this.key(e, "up")} + > + <div class="leading"><slot name="icon"></slot></div> + <div class="header"> + <slot></slot> + </div> + </div>`; + } + + static styles = [styles, super.styles || []]; +} + +declare global { + interface HTMLElementTagNameMap { + "zeta-dropdown-menu-item": ZetaDropdownMenuItem; + } +} diff --git a/src/components/fab/fab.styles.js b/src/components/fab/fab.styles.js new file mode 100644 index 0000000..88e1c15 --- /dev/null +++ b/src/components/fab/fab.styles.js @@ -0,0 +1,134 @@ +import { css } from "lit"; +export default css` + :host { + display: flex; + height: fit-content; + width: fit-content; + cursor: pointer; + flex-direction: column; + align-items: center; + gap: var(--spacing-minimum); + transition: all 0.3s ease-in-out; + } + + .label { + font-size: var(--spacing-medium); + line-height: var(--spacing-large); + /** TODO: Change to semantic token */ + } + + /** Note: Selector styles the button when not extended. */ + :host > .label { + padding-top: var(--spacing-minimum); + } + + /** Note: Necessary to override default zeta-button disabled styles. */ + :host([disabled]) > .label { + background-color: transparent; + } + + /** SIZING START */ + :host button { + width: min-content; + transition: all 0.3s ease-in-out; + display: flex; + align-items: center; + border: none; + justify-content: center; + overflow-x: ellipsis; + font: var(--label-large); + gap: var(--spacing-small); + } + + :host([size="large"]) > button { + padding: var(--spacing-xl); + --icon-size: 36px; + width: calc(var(--spacing-xl) * 2 + 36px); + } + + :host([size="medium"]) > button, + :host([size="small"]) > button { + padding: var(--spacing-medium); + --icon-size: 24px; + width: calc(var(--spacing-medium) * 2 + 24px); + } + + :host([extended]) > button { + padding: var(--spacing-small) var(--spacing-medium); + --icon-size: 24px; + } + :host([extended][size]) > button { + width: min-content; + } + /** SIZING END */ + + /** BORDER RADIUS START */ + :host > button { + border-radius: var(--radius-none); + } + + :host([round="true"][size="small"]) > button, + :host([round="true"][size="medium"]) > button, + :host([round="true"][extended]) > button { + border-radius: var(--radius-rounded); + } + + :host([round="true"][size="large"]:not([extended])) > button { + border-radius: var(--radius-large); + } + + :host([round="full"]) > button, + :host([round="full"][size]) > button { + border-radius: var(--radius-full); + } + /** BORDER RADIUS END */ + + /** FLAVOR START */ + :host([flavor="primary"]:not([disabled])) { + zeta-icon { + --icon-color: var(--main-inverse); + } + } + + :host([flavor="secondary"]:not([disabled])) { + > button { + background-color: var(--state-secondary-enabled); + /** TODO: Change to semantic token */ + zeta-icon { + --icon-color: var(--main-default); + } + } + + > button:hover { + background-color: var(--state-secondary-hover); + /** TODO: Change to semantic token */ + } + + > button:active { + background-color: var(--state-secondary-selected); + /** TODO: Change to semantic token */ + } + } + + :host([flavor="inverse"]:not([disabled])) { + > button { + background-color: var(--state-inverse-selected); + zeta-icon { + --icon-color: var(--main-inverse); + } + } + + > button:hover { + background-color: var(--state-inverse-hover); + } + } + + :host([flavor="inverse"]:not([disabled])[extended]) > button > .label { + color: var(--main-inverse); + } + :host([flavor="secondary"]:not([disabled])[extended]) > button > .label { + color: var(--main-default); + } + + /** FLAVOR END */ +`; diff --git a/src/components/fab/fab.ts b/src/components/fab/fab.ts new file mode 100644 index 0000000..c9dc83a --- /dev/null +++ b/src/components/fab/fab.ts @@ -0,0 +1,76 @@ +import { html, nothing } from "lit"; +import { customElement, property } from "lit/decorators.js"; +import styles from "./fab.styles.js"; +import "../icon/icon.js"; +import { ifDefined } from "lit/directives/if-defined.js"; +import { Flavored, type Flavor } from "../../mixins/flavor.js"; +import { BaseButton } from "../button/base-button.js"; + +export type FabFlavor = Exclude<Flavor, "positive" | "negative" | "outline" | "outline-subtle" | "text">; + +/** Floating action buttons are used for a promoted action. + * + * @slot - The icon of the button. Entered as a plain string. + * + * @figma https://www.figma.com/file/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?node-id=21816-4283&m=dev + * @storybook https://zeta-ds.web.app/?path=/docs/fab--docs + */ + +@customElement("zeta-fab") +export class ZetaFab extends Flavored(BaseButton) { + static get styles() { + return [super.styles ?? [], styles]; + } + /** + * The label display on or below the button. + */ + @property({ type: String }) label: string = ""; + + @property({ type: String, reflect: true }) flavor: FabFlavor = "primary"; + + _round: boolean | "full" = "full"; + + /** + * The border radius of the button. Used in place of rounded prop. + */ + @property({ type: String, reflect: true }) + get round(): boolean | "full" { + return this._round; + } + set round(value: boolean | "full") { + const translatedValue: boolean | "full" = `${value}`.toLowerCase() === "true" ? true : `${value}`.toLowerCase() === "full" ? "full" : false; + this.rounded = !!translatedValue; + this._round = translatedValue; + } + + /** + * @internal + */ + @property({ type: Boolean, reflect: true }) rounded: boolean = false; + + /** + * Whether or not the FAB is extended. + */ + @property({ type: Boolean, reflect: true }) extended: boolean = false; + + @property({ type: String, reflect: true }) size: "small" | "large" = "small"; + + private getLabel() { + return this.label ? html`<div class="label">${this.label}</div>` : nothing; + } + protected render() { + return html` + <button ?disabled=${this.disabled} value=${ifDefined(this.value)} name=${ifDefined(this.name)} type=${ifDefined(this.type)}> + <zeta-icon .rounded=${this.rounded}><slot></slot></zeta-icon> + ${this.extended ? this.getLabel() : nothing} + </button> + ${this.extended ? nothing : this.getLabel()} + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + "zeta-fab": ZetaFab; + } +} diff --git a/src/components/file-upload/file-upload.styles.js b/src/components/file-upload/file-upload.styles.js new file mode 100644 index 0000000..9dc7c9f --- /dev/null +++ b/src/components/file-upload/file-upload.styles.js @@ -0,0 +1,48 @@ +import { css } from "lit"; +export default css` + .file-upload { + border: var(--border-size-medium) dashed var(--border-default); + padding: var(--spacing-4xl); + display: flex; + flex-direction: column; + text-align: center; + align-items: center; + gap: var(--spacing-large); + user-select: none; + } + + :host([active]) .file-upload { + border-color: var(--border-primary); + background-color: var(--surface-selected); + } + + .main-content { + display: flex; + flex-direction: column; + text-align: center; + align-items: center; + gap: var(--spacing-small); + } + + h1 { + font: var(--title-medium); + margin: 0; + pointer-events: none; + } + + h2 { + font: var(--body-small); + color: var(--main-subtle); + padding: 0 var(--spacing-large); + margin: 0; + pointer-events: none; + } + + :host([error]) h2.caption { + color: var(--main-negative); + } + + input { + display: none; + } +`; diff --git a/src/components/file-upload/file-upload.ts b/src/components/file-upload/file-upload.ts new file mode 100644 index 0000000..7303a9f --- /dev/null +++ b/src/components/file-upload/file-upload.ts @@ -0,0 +1,155 @@ +import { html, LitElement, nothing } from "lit"; +import { customElement, property, query } from "lit/decorators.js"; +import styles from "./file-upload.styles.js"; + +import { msg } from "@lit/localize"; +import { Contourable } from "../../mixins/mixins.js"; +import { ifDefined } from "lit/directives/if-defined.js"; +import "../button/button.js"; + +/** + * A file input that supports drag and drop. + * + * @figma https://www.figma.com/file/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?node-id=898-10794 + * @storybook https://zeta-ds.web.app/web/storybook/?path=/docs/file-upload--docs + */ +@customElement("zeta-file-upload") +export class ZetaFileUpload extends Contourable(LitElement) { + private defaultHeadline = msg("Drop files here to upload"); + private errorMsg: string = ""; + + /** The headline text. */ + @property({ type: String }) headline: string = this.defaultHeadline; + + /** The caption text. */ + @property({ type: String, reflect: true }) caption?: string; + + /** + * A comma separated list of accepted file formats. + * + * For more information see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#additional_attributes. + */ + @property({ type: String }) accept?: string; + + /** Allows multiple files to be added to the input. Also affects drag and drop. */ + @property({ type: Boolean }) multiple = true; + + /** The name given to the file input.*/ + @property({ type: String }) name?: string; + + /** Used to trigger the 'active' state of the file input. */ + @property({ type: Boolean, reflect: true }) active: boolean = false; + + /** Use to show the 'error' state of the file input. */ + @property({ type: Boolean, reflect: true }) error: boolean = false; + + @query("input") fileInput?: HTMLInputElement; + + private checkMultiple(e: DragEvent) { + return this.multiple || (!this.multiple && e.dataTransfer?.items && e.dataTransfer?.items.length == 1); + } + + private checkTypes(files: FileList) { + if (!this.accept || this.accept.length === 0) return true; + const allowedExtensions = this.accept?.split(","); + + let filesAccepted = true; + for (let i = 0; i < files.length; i++) { + const file = files.item(i); + let typeMatch = false; + for (const ext of allowedExtensions) { + typeMatch = this.typesEqual(file!.type, ext); + if (typeMatch) break; + } + + if (!typeMatch) { + filesAccepted = false; + break; + } + } + + return filesAccepted; + } + + private typesEqual(fileType: string, allowedType: string): boolean { + // MIME types are equal eg image/jpg + if (fileType === allowedType) return true; + + // E.g. fileType is image/png, allowedType is .png + const t = allowedType.replaceAll(".", ""); + if (fileType.includes(t)) return true; + + // E.g. fileType is image/png, allowedType is image/* + const allowedSplit = allowedType.split("/"); + const fileTypeSplit = fileType.split("/"); + if (allowedSplit[1] == "*" && allowedSplit[0] == fileTypeSplit[0]) return true; + + return false; + } + + private dropHandler = (e: DragEvent) => { + e.preventDefault(); + this.active = false; + this.error = false; + + if (!this.checkTypes(e.dataTransfer!.files)) { + this.errorMsg = msg("Selection contains files with invalid types."); + this.error = true; + } else if (this.checkMultiple(e) && e.dataTransfer?.items && this.fileInput) { + this.fileInput.files = e.dataTransfer.files; + } + }; + + private dragOverHandler = (e: DragEvent) => { + e.preventDefault(); + + if (!this.checkMultiple(e)) { + this.errorMsg = msg("Multiple files are not allowed."); + this.error = true; + } else { + this.active = true; + } + }; + + private dragLeaveHandler = (e: Event) => { + e.preventDefault(); + this.active = false; + this.error = false; + }; + + private mouseLeaveHandler = () => { + this.error = false; + }; + + private openFileInput = () => { + this.fileInput?.click(); + }; + + protected override render() { + return html` + <div + class="file-upload" + @drop=${this.dropHandler} + @dragover=${this.dragOverHandler} + @dragleave=${this.dragLeaveHandler} + @mouseleave=${this.mouseLeaveHandler} + > + <div class="main-content"> + <h1>${this.headline}</h1> + <h2>${msg("or")}</h2> + <zeta-button .rounded=${this.rounded} @click=${this.openFileInput}>Select Files</zeta-button> + </div> + ${this.errorMsg || this.caption ? html`<h2 class="caption">${this.errorMsg || this.caption}</h2>` : nothing} + <input type="file" id=${this.id} name=${ifDefined(this.name)} accept=${ifDefined(this.accept)} .multiple=${this.multiple} /> + </div> + `; + } + + static styles = [super.styles ?? [], styles]; +} + +declare global { + interface HTMLElementTagNameMap { + "zeta-file-upload": ZetaFileUpload; + } +} diff --git a/src/components/global-header/global-header.styles.js b/src/components/global-header/global-header.styles.js new file mode 100644 index 0000000..036b83f --- /dev/null +++ b/src/components/global-header/global-header.styles.js @@ -0,0 +1,47 @@ +import { css } from "lit"; +export default css` + :host { + display: block; + min-width: min-content; + display: flex; + flex-direction: column; + background-color: var(--surface-default); + --tab-bar-background: var(--surface-default); + color: var(--main-default); + } + + ::slotted(zeta-icon-button) { + --icon-button-icon-color: var(--main-default); + --icon-button-icon-color-disabled: var(--main-disabled); + --icon-button-color: var(--surface-default); + } + + .slotted-content, + .leading, + .global-header { + display: flex; + align-items: center; + } + + .global-header { + gap: var(--spacing-2xl); + justify-content: space-between; + padding: var(--spacing-small) var(--spacing-2xl); + } + + .slotted-content { + gap: var(--spacing-small); + } + + .leading { + gap: var(--spacing-2xl); + } + + .header { + font: var(--title-large); + } + + .navigation-menu { + padding: 0 var(--spacing-small); + } +`; diff --git a/src/components/global-header/global-header.ts b/src/components/global-header/global-header.ts new file mode 100644 index 0000000..aa30668 --- /dev/null +++ b/src/components/global-header/global-header.ts @@ -0,0 +1,52 @@ +import { html, LitElement, nothing } from "lit"; +import { customElement, property } from "lit/decorators.js"; +import styles from "./global-header.styles.js"; +import "../icon/icon.js"; +import { Contourable } from "../../mixins/mixins.js"; + +/** + * A header with support for displaying a zeta-navigation-menu + * + * @slot - The main content of the header. + * @slot leading - The leading content on the header. + * @slot navigation-menu - The navigation menu. The position is based on the 'menuPosition' property. + * @slot trailing - The trailing content on the header. + * @part global-header - Styles the header container + * + * @figma https://www.figma.com/file/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?node-id=23144-118110 + * @storybook https://zeta-ds.web.app/web/storybook/?path=/docs/global-header--docs + */ +@customElement("zeta-global-header") +export class ZetaGlobalHeader extends Contourable(LitElement) { + /** The headline text on the header. Can also be slotted. */ + @property({ type: String }) headline?: string; + + /** The position of the navigation. */ + @property({ type: String }) menuPosition: "inline" | "below" = "inline"; + + protected override render() { + return html` + <div class="global-header" part="global-header"> + <div class="leading"> + <div class="slotted-content"> + <slot name="leading"></slot> + <div class="header">${this.headline}<slot></slot></div> + </div> + ${this.menuPosition == "inline" ? html`<slot name="navigation-menu"></slot>` : nothing} + </div> + <div class="slotted-content"> + <slot name="trailing"></slot> + </div> + </div> + ${this.menuPosition == "below" ? html`<div class="navigation-menu"><slot name="navigation-menu"></slot></div>` : nothing} + `; + } + + static styles = [super.styles ?? [], styles]; +} + +declare global { + interface HTMLElementTagNameMap { + "zeta-global-header": ZetaGlobalHeader; + } +} diff --git a/src/components/grid-menu-item/grid-menu-item.styles.js b/src/components/grid-menu-item/grid-menu-item.styles.js new file mode 100644 index 0000000..10fc590 --- /dev/null +++ b/src/components/grid-menu-item/grid-menu-item.styles.js @@ -0,0 +1,49 @@ +import { css } from "lit"; +export default css` + :host { + display: flex; + flex: 1; + width: min-content; + justify-content: center; + cursor: pointer; + flex-direction: column; + gap: var(--spacing-small); + align-items: center; + padding: var(--spacing-small); + min-width: 46px; + } + + :host[label] { + padding: var(--spacing-small) var(--spacing-2xl); + } + + .label { + color: var(--main-subtle); + font: var(--label-small); + user-select: none; + } + + :host([active]) .label { + color: var(--main-primary); + } + + .icon-container { + position: relative; + } + + zeta-notification-indicator { + position: absolute; + top: -2px; + right: -2px; + border: var(--border-size-medium) solid var(--surface-default); + border-radius: var(--radius-full); + } + + ::slotted(zeta-icon) { + --icon-color: red; + /* --icon-color: var(--main-subtle); */ + } + :host([active]) ::slotted(zeta-icon) { + --icon-color: var(--main-primary); + } +`; diff --git a/src/components/grid-menu-item/grid-menu-item.ts b/src/components/grid-menu-item/grid-menu-item.ts new file mode 100644 index 0000000..1686103 --- /dev/null +++ b/src/components/grid-menu-item/grid-menu-item.ts @@ -0,0 +1,41 @@ +import { customElement, property } from "lit/decorators.js"; +import { Contourable } from "../../mixins/mixins.js"; +import { html, LitElement } from "lit"; +import styles from "./grid-menu-item.styles.js"; +import "../badges/indicators/indicators"; + +// TODO(UX-1335): Grid items are not working in storybook +/** + * An item to be used in a grid menu. Current usecases include the navigation bar and bottom sheet. + * + * @slot - Label to be displayed. + * @slot {zeta-icon} icon - Icon to be displayed. Full list of icons can be found at https://zeta-icons.web.app/. + * + * @figma https://www.figma.com/file/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?node-id=21186-41419 + * @storybook https://zeta-ds.web.app/web/storybook/?path=/docs/navigation-bar--docs + */ +@customElement("zeta-grid-menu-item") +export class ZetaGridMenuItem extends Contourable(LitElement) { + @property({ type: String, reflect: true }) notificationValue?: string | boolean; + @property({ type: Boolean, reflect: true }) active: boolean = false; + + protected render() { + return html` + <div class="icon-container"> + ${this.notificationValue ? html`<zeta-notification-indicator .value=${this.notificationValue}></zeta-notification-indicator>` : ""} + <slot name="icon"></slot> + </div> + <div class="label"> + <slot></slot> + </div> + `; + } + + static styles = [styles, super.styles || []]; +} + +declare global { + interface HTMLElementTagNameMap { + "zeta-grid-menu-item": ZetaGridMenuItem; + } +} diff --git a/src/components/icon/icon.styles.js b/src/components/icon/icon.styles.js new file mode 100644 index 0000000..1a5fb51 --- /dev/null +++ b/src/components/icon/icon.styles.js @@ -0,0 +1,35 @@ +import { css /*unsafeCSS*/ } from "lit"; +// import * as styles from "@zebra-fed/zeta-icons/index.css" assert { type: "css" }; +export default [ + // unsafeCSS(styles), + css` + :host { + display: flex; + flex-shrink: 0; + line-height: 1; + max-width: initial; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + font-weight: normal; + font-style: normal; + line-height: 1; + text-transform: none; + letter-spacing: normal; + word-wrap: normal; + white-space: nowrap; + direction: ltr; + -webkit-font-smoothing: antialiased; + text-rendering: optimizeLegibility; + -moz-osx-font-smoothing: grayscale; + font-feature-settings: "liga"; + user-select: none; + height: var(--icon-size, 24px); + width: var(--icon-size, 24px); + transition: all 0.3s ease-in-out; + color: var(--icon-color, var(--main-default)); + font-size: var(--icon-size, 24px); + line-height: var(--icon-size, 1); + font-family: var(--icon-font, "zeta-icons-round"); + } + ` +]; diff --git a/src/components/icon/icon.ts b/src/components/icon/icon.ts new file mode 100644 index 0000000..34d6d66 --- /dev/null +++ b/src/components/icon/icon.ts @@ -0,0 +1,40 @@ +import { html, LitElement, nothing } from "lit"; +import { customElement, property } from "lit/decorators.js"; +import styles from "./icon.styles.js"; +import { Contourable } from "../../mixins/mixins.js"; + +/** + * @cssproperty --icon-size the width/height of the icon + * @cssproperty --icon-color the color of the icon + * @slot - {ZetaIconName} Name of icon to be displayed. Full list of icons can be found at {@link https://zeta-icons.web.app/ Zeta Icons} + */ +@customElement("zeta-icon") +export class ZetaIcon extends Contourable(LitElement) { + /** + * Size of icon as css variable. + * + * If a Number is provided, will fallback to px. + * @deprecated Use the CSS Variable "--icon-size" instead + */ + @property() size?: string | number; + + /** + * Color of icon as css value + * @deprecated Use the CSS Variable "--icon-color" instead + */ + @property({ type: String }) color?: string; + + private sizeIsNum = () => typeof this.size === "number" || !isNaN(this.size as unknown as number); + + protected render() { + const size = this.sizeIsNum() ? this.size + "px" : this.size; + return html` <style> + :host { + ${this.size ? `--icon-size: ${size}` : nothing}; + ${this.color ? `--icon-color: ${this.color}` : nothing}; + } + </style> + <slot @slotchange=${() => this.requestUpdate()}></slot>`; + } + static styles = [styles, super.styles || []]; +} diff --git a/src/components/in-page-banner/in-page-banner.styles.js b/src/components/in-page-banner/in-page-banner.styles.js new file mode 100644 index 0000000..82dd00c --- /dev/null +++ b/src/components/in-page-banner/in-page-banner.styles.js @@ -0,0 +1,118 @@ +import { css } from "lit"; +export default css` + :host { + display: flex; + flex-direction: row; + padding: var(--spacing-medium); + justify-content: center; + align-items: center; + border: var(--border-size-small) solid; + color: var(--main-default); + + > .leading { + padding-right: var(--spacing-small); + padding-top: var(--spacing-0-5); + align-self: stretch; + --icon-size: 20px; + } + + > .trailing { + flex: 1; + display: flex; + flex-direction: column; + gap: var(--spacing-minimum); + + > .header { + display: flex; + flex-direction: row; + justify-content: space-between; + + zeta-icon-button { + margin-top: -4px; + margin-right: -4px; + --icon-button-color: transparent; + --icon-button-icon-color: var(--main-default); + cursor: pointer; + } + + > .title { + font: var(--label-large); + } + } + > .body { + margin-top: var(--spacing-minimum); + margin-right: var(--spacing-3xl); + font: var(--body-small); + } + + > .content { + max-width: calc(100% - var(--spacing-3xl)); + + > ::slotted(*) { + max-width: 100%; + height: auto; + } + } + + > .footer { + display: flex; + flex-direction: row; + gap: var(--spacing-small); + max-width: calc(100% - var(--spacing-3xl)); + } + } + } + + :host([rounded]), + :host([rounded]) .content ::slotted(*) { + border-radius: var(--radius-minimal); + } + + :host([status="default"]) { + border-color: var(--border-default); + background: var(--surface-default); + fill: var(--main-default); + } + + :host([status="info"]) { + border-color: var(--border-info); + background: var(--surface-info-subtle); + zeta-icon { + --icon-color: var(--main-info); + } + } + + :host([status="positive"]) { + border-color: var(--border-positive); + background: var(--surface-positive-subtle); + zeta-icon { + --icon-color: var(--main-positive); + } + } + + :host([status="warning"]) { + border-color: var(--border-warning); + background: var(--surface-warning-subtle); + + zeta-icon { + --icon-color: var(--main-warning); + } + } + + :host([status="negative"]) { + border-color: var(--border-negative); + background: var(--surface-negative-subtle); + zeta-icon { + --icon-color: var(--main-negative); + } + } + + ::slotted([slot="action"]) { + margin-top: var(--spacing-large); + } + + ::slotted([slot="leadingAction"]:not(zeta-button)), + ::slotted([slot="trailingAction"]:not(zeta-button)) { + display: none; + } +`; diff --git a/src/components/in-page-banner/in-page-banner.ts b/src/components/in-page-banner/in-page-banner.ts new file mode 100644 index 0000000..60fb469 --- /dev/null +++ b/src/components/in-page-banner/in-page-banner.ts @@ -0,0 +1,71 @@ +import { html, LitElement } from "lit"; +import { customElement, property } from "lit/decorators.js"; +import styles from "./in-page-banner.styles.js"; +import { Contourable } from "../../mixins/mixins.js"; +import "../button/icon-button/icon-button.js"; + +/** + * Zeta in page banner component. + * + * In page banners display an important, succinct message, and may provide actions for users to address. Banners should be displayed at the top of the screen,below a top app bar. Only one banner should be shown at a time. + * + * This component represents a banner that can be displayed within a page. + * It can have a title, body text, and various status options. + * + * @slot - The main content of the banner. + * @slot action - The action buttons. + * + * @figma https://www.figma.com/file/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?node-id=21156-27071 + * @storybook https://zeta-ds.web.app/web/storybook/?path=/docs/in-page-banner--docs + */ +@customElement("zeta-in-page-banner") +export class ZetaInPageBanner extends Contourable(LitElement) { + /** + * Title of the banner, displayed at the top. + */ + @property({ type: String }) title: string = ""; + + /** + * Status of the component. + */ + @property({ type: String, reflect: true }) status: "default" | "info" | "positive" | "warning" | "negative" = "default"; + + static styles = [styles, super.styles ?? []]; + + private getIcon = () => { + switch (this.status) { + case "positive": + return "check_circle"; + case "negative": + return "error"; + case "default": + return "info"; + default: + return this.status; + } + }; + + protected render() { + return html` + <div class="leading"><zeta-icon .rounded=${this.rounded}>${this.getIcon()}</zeta-icon></div> + <div class="trailing"> + <div class="header"> + <div class="title">${this.title}</div> + <zeta-icon-button flavor="text" size="small" .rounded=${this.rounded} .onclick=${() => this.remove()}>close</zeta-icon-button> + </div> + <div class="content"> + <slot></slot> + </div> + <div class="footer"> + <slot name="action"></slot> + </div> + </div> + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + "zeta-in-page-banner": ZetaInPageBanner; + } +} diff --git a/src/components/list/list-item/list-item.styles.js b/src/components/list/list-item/list-item.styles.js new file mode 100644 index 0000000..6606349 --- /dev/null +++ b/src/components/list/list-item/list-item.styles.js @@ -0,0 +1,27 @@ +import { css } from "lit"; +export default css` + .list-item { + padding: var(--spacing-xl) var(--spacing-large); + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--spacing-medium); + box-shadow: inherit; + } + + h1 { + font: var(--title-medium); + margin: 0; + } + + .leading { + display: flex; + gap: inherit; + align-items: center; + } + + .trailing { + display: flex; + justify-self: flex-end; + } +`; diff --git a/src/components/list/list-item/list-item.ts b/src/components/list/list-item/list-item.ts new file mode 100644 index 0000000..d33c3eb --- /dev/null +++ b/src/components/list/list-item/list-item.ts @@ -0,0 +1,40 @@ +import { customElement, property } from "lit/decorators.js"; +import { LitElement, html } from "lit"; +import styles from "./list-item.styles.js"; + +/** + * List items are used in lists. + * + * @slot leading - Content placed before the headline + * @slot trailing - Content placed after the headline + * + * @figma https://www.figma.com/design/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?node-id=229-17&node-type=canvas&m=dev + * @storybook https://zeta-ds.web.app/web/storybook/index.html?path=/docs/list--docs + */ +@customElement("zeta-list-item") +export class ZetaListItem extends LitElement { + /** The headline text of the list element. */ + @property({ type: String }) headline?: string; + + protected render() { + return html` + <div class="list-item"> + <div class="leading"> + <slot name="leading"></slot> + <h1>${this.headline}</h1> + </div> + <div class="trailing"> + <slot name="trailing"></slot> + </div> + </div> + `; + } + + static styles = [styles]; +} + +declare global { + interface HTMLElementTagNameMap { + "zeta-list-item": ZetaListItem; + } +} diff --git a/src/components/list/list.styles.js b/src/components/list/list.styles.js new file mode 100644 index 0000000..b9cbaec --- /dev/null +++ b/src/components/list/list.styles.js @@ -0,0 +1,6 @@ +import { css } from "lit"; +export default css` + :host([divide]) ::slotted(zeta-list-item:not(:last-child)) { + box-shadow: 0 var(--border-size-medium) 0 -1px var(--border-default); + } +`; diff --git a/src/components/list/list.ts b/src/components/list/list.ts new file mode 100644 index 0000000..4ae3beb --- /dev/null +++ b/src/components/list/list.ts @@ -0,0 +1,30 @@ +import { LitElement, html } from "lit"; +import { customElement, property } from "lit/decorators.js"; +import styles from "./list.styles.js"; + +export * from "./list-item/list-item.js"; +/** + * Lists display lists of list items. + * + * @slot - The list items. Should be a collection of `zeta-list-item`s. + * + * @figma https://www.figma.com/design/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?node-id=229-17&node-type=canvas&m=dev + * @storybook https://zeta-ds.web.app/web/storybook/index.html?path=/docs/list--docs + */ +@customElement("zeta-list") +export class ZetaList extends LitElement { + /** Adds dividers in between the list items.*/ + @property({ type: Boolean, reflect: true }) divide: boolean = false; + + protected render() { + return html` <div class="list"><slot></slot></div> `; + } + + static styles = [styles]; +} + +declare global { + interface HTMLElementTagNameMap { + "zeta-list": ZetaList; + } +} diff --git a/src/components/navigation-bar/navigation-bar.styles.js b/src/components/navigation-bar/navigation-bar.styles.js new file mode 100644 index 0000000..a85951f --- /dev/null +++ b/src/components/navigation-bar/navigation-bar.styles.js @@ -0,0 +1,51 @@ +import { css } from "lit"; +export default css` + :host { + border-top: var(--border-size-small) solid var(--border-default); + display: flex; + /* justify-content: space-around; */ + align-items: stretch; + } + + ::slotted(.divider)::before { + content: ""; + width: var(--border-size-small); + background-color: var(--border-default); + display: block; + height: 100%; + position: absolute; + left: 50%; + } + + ::slotted(.divider) { + display: flex; + flex: 2; + position: relative; + margin: var(--spacing-small) 0; + } + + ::slotted(.spacer) { + display: flex; + flex: 1; + } + + :host([shrinkItems]) ::slotted(zeta-grid-menu-item) { + width: 62px; + flex: 0; + } + + :host([shrinkItems]) ::slotted(zeta-button) { + flex: 0; + } + + :host([shrinkItems]) ::slotted(.divider) { + flex: 0; + width: 62px; + margin: 0 var(--spacing-6xl); + } + + ::slotted(zeta-button) { + margin: var(--spacing-large) var(--spacing-2xl); + flex: 1; + } +`; diff --git a/src/components/navigation-bar/navigation-bar.ts b/src/components/navigation-bar/navigation-bar.ts new file mode 100644 index 0000000..12335da --- /dev/null +++ b/src/components/navigation-bar/navigation-bar.ts @@ -0,0 +1,30 @@ +import { customElement, property } from "lit/decorators.js"; +import { LitElement, html } from "lit"; +import styles from "./navigation-bar.styles.js"; + +export * from "../grid-menu-item/grid-menu-item.js"; +/** + * Navigation Bars (Bottom navigation) allow movement between primary destinations in an app. + * + * @slot - A collection of 'zeta-grid-menu-item's to be displayed in the navigation bar. + * + * @figma https://www.figma.com/file/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?node-id=21186-40498 + * @storybook https://zeta-ds.web.app/web/storybook/?path=/docs/navigation-bar--docs + */ +@customElement("zeta-navigation-bar") +export class ZetaNavigationBar extends LitElement { + @property({ type: Boolean, reflect: true }) shrinkItems: boolean = false; + + protected render() { + return html`<slot></slot>`; + } + + static styles = [styles, super.styles || []]; + // static styles = [styles ContourableElement.styles || []]; //TODO: Add contourable back, check styles. +} + +declare global { + interface HTMLElementTagNameMap { + "zeta-navigation-bar": ZetaNavigationBar; + } +} diff --git a/src/components/navigation-drawer/navigation-drawer-footer/navigation-drawer-footer.styles.js b/src/components/navigation-drawer/navigation-drawer-footer/navigation-drawer-footer.styles.js new file mode 100644 index 0000000..ef4f995 --- /dev/null +++ b/src/components/navigation-drawer/navigation-drawer-footer/navigation-drawer-footer.styles.js @@ -0,0 +1,68 @@ +import { css } from "lit"; +export default css` + .drawer-footer-profile { + padding: var(--spacing-large); + background: var(--surface-default-inverse); + display: flex; + gap: var(--spacing-large); + justify-content: space-between; + align-items: center; + + .main-content { + display: flex; + flex-direction: column; + } + + h1 { + font: var(--body-small); + color: var(--main-inverse); + margin: 0; + } + + h2 { + font: var(--body-x-small); + color: var(--main-subtle); + margin: 0; + } + + .leading { + display: flex; + gap: inherit; + align-items: center; + } + + .trailing { + display: flex; + justify-self: flex-end; + } + } + + :host([hideDefaultLogo]) .logo { + display: none; + } + + .drawer-footer-logo { + padding: var(--spacing-medium) var(--spacing-large); + background: var(--surface); + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + + .logo { + width: var(--spacing-20); + height: var(--spacing-4xl); + } + + h3 { + font: var(--body-x-small); + color: var(--main-subtle); + text-align: center; + font-weight: 400; + } + } + + :host([divide]) footer { + box-shadow: 0 -1px 0 0 var(--border-subtle); + } +`; diff --git a/src/components/navigation-drawer/navigation-drawer-footer/navigation-drawer-footer.ts b/src/components/navigation-drawer/navigation-drawer-footer/navigation-drawer-footer.ts new file mode 100644 index 0000000..e6a59ad --- /dev/null +++ b/src/components/navigation-drawer/navigation-drawer-footer/navigation-drawer-footer.ts @@ -0,0 +1,88 @@ +import { LitElement, html, nothing } from "lit"; +import { unsafeSVG } from "lit/directives/unsafe-svg.js"; +import { customElement, property, queryAssignedElements } from "lit/decorators.js"; +import styles from "./navigation-drawer-footer.styles.js"; +import ZebraLogo from "../../../../assets/zebra-logo.svg"; + +/** + * The footer used on a navigation drawer. + * + * @slot - The headline text. + * @slot leading - Content placed before the headline. Not shown if 'variant' is set to 'logo'. + * @slot trailing - Content placed after the headline. Not shown if 'variant' is set to 'logo'. + * @slot logo - The element that replaces the default Zebra logo. Not shown if 'variant' is set to 'profile'. + * + * @figma https://www.figma.com/design/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?node-id=1075-21296&node-type=canvas&m=dev + * @storybook https://zeta-ds.web.app/web/storybook/index.html?path=/docs/navigation-drawer--docs + */ +@customElement("zeta-navigation-drawer-footer") +export class ZetaNavigationDrawerFooter extends LitElement { + /** The headline text. Can also be slotted. */ + @property({ type: String }) headline?: string; + + /** The sub headline text. */ + @property({ type: String }) subHeadline?: string; + + /** Shows a divider above the footer. */ + @property({ type: Boolean, reflect: true }) divide: boolean = false; + + /** + * The variant of the footer. + * + * If set to 'logo' the zebra logo or the contents of the 'logo' slot will be shown and the headline text will be displayed beneath the logo. + */ + @property({ type: String, reflect: true }) variant: "profile" | "logo" = "profile"; + + @property({ type: Boolean, reflect: true }) hideDefaultLogo?: boolean; + @queryAssignedElements({ slot: "logo", flatten: true }) customLogo!: NodeList; + + private getProfileFooter() { + return html`<footer class="drawer-footer-profile"> + <div class="leading"> + <slot name="leading"></slot> + <div class="main-content"> + <h1>${this.headline}<slot></slot></h1> + ${this.subHeadline ? html`<h2>${this.subHeadline}</h2>` : nothing} + </div> + </div> + <div class="trailing"> + <slot name="trailing"></slot> + </div> + </footer>`; + } + + private handleSlotChange = () => { + this.requestUpdate(); + if (this.customLogo.length > 0) { + this.hideDefaultLogo = true; + } else { + this.hideDefaultLogo = false; + } + }; + + private getLogoFooter() { + // TODO remove unsafeSVG and try and use svg instead + return html`<footer class="drawer-footer-logo"> + <slot name="logo" @slotchange=${this.handleSlotChange}></slot> + <div class="logo">${unsafeSVG(ZebraLogo)}</div> + <h3>${this.headline}<slot></slot></h3> + </footer>`; + } + + protected override render() { + switch (this.variant) { + case "profile": + return this.getProfileFooter(); + case "logo": + return this.getLogoFooter(); + } + } + + static styles = [styles]; +} + +declare global { + interface HTMLElementTagNameMap { + "zeta-navigation-drawer-footer": ZetaNavigationDrawerFooter; + } +} diff --git a/src/components/navigation-drawer/navigation-drawer-header/navigation-drawer-header.styles.js b/src/components/navigation-drawer/navigation-drawer-header/navigation-drawer-header.styles.js new file mode 100644 index 0000000..0e6ec11 --- /dev/null +++ b/src/components/navigation-drawer/navigation-drawer-header/navigation-drawer-header.styles.js @@ -0,0 +1,43 @@ +import { css } from "lit"; +export default css` + .drawer-header { + padding: var(--spacing-2xl) var(--spacing-large); + background: var(--surface-default-inverse); + display: flex; + gap: var(--spacing-large); + justify-content: space-between; + align-items: center; + } + + :host([divide]) .drawer-header { + box-shadow: 0 var(--border-size-small) 0 0 var(--border-subtle); + } + + .main-content { + display: flex; + flex-direction: column; + } + + h1 { + font: var(--title-medium); + color: var(--main-inverse); + margin: 0; + } + + h2 { + font: var(--body-x-small); + color: var(--main-subtle); // TODO May need to be changed when the designs get updated + margin: 0; + } + + .leading { + display: flex; + gap: inherit; + align-items: center; + } + + .trailing { + display: flex; + justify-self: flex-end; + } +`; diff --git a/src/components/navigation-drawer/navigation-drawer-header/navigation-drawer-header.ts b/src/components/navigation-drawer/navigation-drawer-header/navigation-drawer-header.ts new file mode 100644 index 0000000..77c0084 --- /dev/null +++ b/src/components/navigation-drawer/navigation-drawer-header/navigation-drawer-header.ts @@ -0,0 +1,48 @@ +import { LitElement, html } from "lit"; +import { customElement, property } from "lit/decorators.js"; +import styles from "./navigation-drawer-header.styles.js"; + +/** + * The header used on a navigation drawer. + * + * @slot - The headline text. + * @slot leading - Content placed before the headline. + * @slot trailing - Content placed after the headline. + * + * @figma https://www.figma.com/design/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?node-id=1075-21296&node-type=canvas&m=dev + * @storybook https://zeta-ds.web.app/web/storybook/index.html?path=/docs/navigation-drawer--docs + */ +@customElement("zeta-navigation-drawer-header") +export class ZetaNavigationDrawerHeader extends LitElement { + /** The headline text. Can also be slotted. */ + @property({ type: String }) headline?: string; + + /** The sub headline text.*/ + @property({ type: String }) subHeadline?: string; + + /** Shows a divider below the header. */ + @property({ type: Boolean, reflect: true }) divide: boolean = false; + + protected override render() { + return html`<header class="drawer-header"> + <div class="leading"> + <slot name="leading"></slot> + <div class="main-content"> + <h1>${this.headline}<slot></slot></h1> + <h2>${this.subHeadline}</h2> + </div> + </div> + <div class="trailing"> + <slot name="trailing"></slot> + </div> + </header>`; + } + + static styles = [styles]; +} + +declare global { + interface HTMLElementTagNameMap { + "zeta-navigation-drawer-header": ZetaNavigationDrawerHeader; + } +} diff --git a/src/components/navigation-drawer/navigation-drawer-item/navigation-drawer-item.styles.js b/src/components/navigation-drawer/navigation-drawer-item/navigation-drawer-item.styles.js new file mode 100644 index 0000000..b09fcb4 --- /dev/null +++ b/src/components/navigation-drawer/navigation-drawer-item/navigation-drawer-item.styles.js @@ -0,0 +1,39 @@ +import { css } from "lit"; +export default css` + :host { + padding: var(--spacing-medium); + background: var(--surface-default); + display: flex; + gap: var(--spacing-large); + justify-content: space-between; + align-items: center; + color: var(--main-subtle); + } + + :host(:not([disabled])[active]) { + background-color: var(--surface-selected); + } + + :host(:not([disabled]):hover) { + color: var(--main-default); + } + + h1 { + display: flex; + font: var(--title-medium); + margin: 0; + } + + .leading { + display: flex; + gap: inherit; + align-items: center; + } + + .trailing { + display: flex; + gap: var(--spacing-small); + align-items: center; + justify-self: flex-end; + } +`; diff --git a/src/components/navigation-drawer/navigation-drawer-item/navigation-drawer-item.ts b/src/components/navigation-drawer/navigation-drawer-item/navigation-drawer-item.ts new file mode 100644 index 0000000..f762832 --- /dev/null +++ b/src/components/navigation-drawer/navigation-drawer-item/navigation-drawer-item.ts @@ -0,0 +1,45 @@ +import { html, LitElement } from "lit"; +import { customElement, property } from "lit/decorators.js"; +import styles from "./navigation-drawer-item.styles.js"; +import { Contourable, Interactive } from "../../../mixins/mixins.js"; + +/** + * A navigation item to be used in a zeta-navigation-drawer + * + * @slot - The headline text. + * @slot badge - Content to be placed in the badge. + * @slot leading - Content to be placed before the headline. + * @slot trailing - Content to be placed after the headline. + * + * @figma https://www.figma.com/design/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?node-id=1075-21296&node-type=canvas&m=dev + * @storybook https://zeta-ds.web.app/web/storybook/index.html?path=/docs/navigation-drawer--docs + */ +@customElement("zeta-navigation-drawer-item") +export class ZetaNavigationDrawerItem extends Contourable(Interactive(LitElement)) { + /** The headline text. Can also be slotted. */ + @property({ type: String }) headline?: string; + + /** Sets the item to active. */ + @property({ type: Boolean, reflect: true }) active: boolean = false; + + protected override render() { + return html` + <div class="leading"> + <slot name="leading"></slot> + <h1>${this.headline}<slot></slot></h1> + </div> + <div class="trailing"> + <slot name="badge"></slot> + <slot name="trailing"></slot> + </div> + `; + } + + static styles = [super.styles ?? [], styles]; +} + +declare global { + interface HTMLElementTagNameMap { + "zeta-navigation-drawer-item": ZetaNavigationDrawerItem; + } +} diff --git a/src/components/navigation-drawer/navigation-drawer-sub-item/navigation-drawer-sub-item.styles.js b/src/components/navigation-drawer/navigation-drawer-sub-item/navigation-drawer-sub-item.styles.js new file mode 100644 index 0000000..c5ab546 --- /dev/null +++ b/src/components/navigation-drawer/navigation-drawer-sub-item/navigation-drawer-sub-item.styles.js @@ -0,0 +1,33 @@ +import { css } from "lit"; +export default css` + :host { + margin-left: var(--spacing-2xl); + } + + .container { + display: flex; + gap: var(--spacing-small); + } + + .border { + border-left: var(--border-size-small) solid var(--border-subtle); + } + + .sub-item { + margin: 0; + padding: var(--spacing-small) var(--spacing-large); + font: var(--title-medium); + display: flex; + flex: 1; + color: var(--main-subtle); + } + + :host([active]:not([disabled])) .sub-item { + background-color: var(--surface-selected); + color: var(--main-default); + } + + :host(:not([disabled]):hover) .sub-item { + color: var(--main-default); + } +`; diff --git a/src/components/navigation-drawer/navigation-drawer-sub-item/navigation-drawer-sub-item.ts b/src/components/navigation-drawer/navigation-drawer-sub-item/navigation-drawer-sub-item.ts new file mode 100644 index 0000000..40b7a38 --- /dev/null +++ b/src/components/navigation-drawer/navigation-drawer-sub-item/navigation-drawer-sub-item.ts @@ -0,0 +1,38 @@ +import { html, LitElement } from "lit"; +import { customElement, property } from "lit/decorators.js"; +import styles from "./navigation-drawer-sub-item.styles.js"; +import { Contourable, Interactive } from "../../../mixins/mixins.js"; + +/** + * A navigation sub item to be used in a zeta-navigation-drawer + * + * @slot - The headline text. + * + * @figma https://www.figma.com/design/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?node-id=1075-21296&node-type=canvas&m=dev + * @storybook https://zeta-ds.web.app/web/storybook/index.html?path=/docs/navigation-drawer--docs + */ +@customElement("zeta-navigation-drawer-sub-item") +export class ZetaNavigationDrawerSubItem extends Contourable(Interactive(LitElement)) { + /** The headline text. Can also be slotted. */ + @property({ type: String }) headline?: string; + + /** Sets the item to active. */ + @property({ type: Boolean, reflect: true }) active: boolean = false; + + protected override render() { + return html` + <div class="container"> + <div class="border"></div> + <h1 class="sub-item interactive-target contourable-target">${this.headline}<slot></slot></h1> + </div> + `; + } + + static styles = [super.styles ?? [], styles]; +} + +declare global { + interface HTMLElementTagNameMap { + "zeta-navigation-drawer-sub-item": ZetaNavigationDrawerSubItem; + } +} diff --git a/src/components/navigation-drawer/navigation-drawer.styles.js b/src/components/navigation-drawer/navigation-drawer.styles.js new file mode 100644 index 0000000..ca11356 --- /dev/null +++ b/src/components/navigation-drawer/navigation-drawer.styles.js @@ -0,0 +1,84 @@ +import { css } from "lit"; +export default css` + dialog { + &[open] { + display: flex; + } + + flex-direction: column; + height: 100%; + max-height: 100vh; + width: 40%; + min-width: 240px; + max-width: 380px; + background: var(--surface-default); + border: none; + margin: 0; + box-shadow: var(--elevation-6); + padding: 0; + top: 0; + overflow: hidden; + } + + :host([anchor="right"]) { + dialog { + margin-left: auto; + transform: translateX(100%); + } + + dialog[open] { + transform: translateX(0); + } + } + + :host([anchor="left"]) { + dialog { + transform: translateX(-100%); + } + + dialog[open] { + transform: translateX(0); + } + } + + :host([showAnimation]) { + dialog { + transition: + transform 0.5s ease, + display 0.5s ease allow-discrete; + } + + &:host([anchor="left"]) { + /*TODO Not supported in Firefox*/ + @starting-style { + dialog[open] { + transform: translateX(-100%); + } + } + } + + &:host([anchor="right"]) { + /*TODO Not supported in Firefox*/ + @starting-style { + dialog[open] { + transform: translateX(100%); + } + } + } + } + + .scrollable-content { + flex: 1; + display: flex; + flex-direction: column; + overflow-y: auto; + } + + .content { + flex-direction: column; + display: flex; + gap: var(--spacing-minimum); + padding: var(--spacing-minimum) var(--spacing-small); + flex: 1; + } +`; diff --git a/src/components/navigation-drawer/navigation-drawer.ts b/src/components/navigation-drawer/navigation-drawer.ts new file mode 100644 index 0000000..6eb3086 --- /dev/null +++ b/src/components/navigation-drawer/navigation-drawer.ts @@ -0,0 +1,62 @@ +import { LitElement, html } from "lit"; +import { customElement, property } from "lit/decorators.js"; +import styles from "./navigation-drawer.styles.js"; +import { Popup } from "../../mixins/mixins.js"; + +export * from "./navigation-drawer-footer/navigation-drawer-footer.js"; +export * from "./navigation-drawer-header/navigation-drawer-header.js"; +export * from "./navigation-drawer-item/navigation-drawer-item.js"; +export * from "./navigation-drawer-sub-item/navigation-drawer-sub-item.js"; + +// TODO: When anchored to the right, the drawer causes page overflow when animating away. + +/** + * Navigation drawers provide access to destinations and app functionality, such as switching accounts. + * They can either be permanently on-screen or controlled by a navigation menu icon + * + * + * @slot - The main content of the drawer. + * @slot header - The drawer header. + * @slot footer - The drawer footer. + * + * @figma https://www.figma.com/design/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?node-id=1075-21296&node-type=canvas&m=dev + * @storybook https://zeta-ds.web.app/web/storybook/index.html?path=/docs/navigation-drawer--docs + */ +@customElement("zeta-navigation-drawer") +export class ZetaNavigationDrawer extends Popup(LitElement) { + /** The side of the screen that the drawer is anchored on. */ + @property({ type: String, reflect: true }) anchor: "left" | "right" = "left"; + + /** Toggles the animation for the navigation drawer. */ + @property({ type: Boolean, reflect: true }) showAnimation: boolean = true; + + /** Whether the modal is initially open. */ + @property({ type: Boolean }) initialOpen: boolean = false; + + static override shadowRootOptions = { + ...LitElement.shadowRootOptions, + delegatesFocus: true + }; + + protected override render() { + return html` + <dialog class="navigation-drawer" @click=${this.onBarrierClicked} id=${this.id} .open=${this.initialOpen}> + <slot name="header"></slot> + <div class="scrollable-content"> + <div class="content"> + <slot></slot> + </div> + <slot name="footer"></slot> + </div> + </dialog> + `; + } + + static styles = [super.styles ?? [], styles]; +} + +declare global { + interface HTMLElementTagNameMap { + "zeta-navigation-drawer": ZetaNavigationDrawer; + } +} diff --git a/src/components/navigation-profile/navigation-profile.styles.js b/src/components/navigation-profile/navigation-profile.styles.js new file mode 100644 index 0000000..2eaed0e --- /dev/null +++ b/src/components/navigation-profile/navigation-profile.styles.js @@ -0,0 +1,31 @@ +import { css } from "lit"; +/*import styles from "../../../mixins/tertiary-interactive.styles.css";*/ +export default css` + :host { + width: min-content; + display: block; + white-space: nowrap; + color: var(--main-inverse); + } + + .navigation-profile { + padding: var(--spacing-minimum) var(--spacing-medium); + display: flex; + align-items: center; + gap: var(--spacing-minimum); + font: var(--title-medium); + + /*@include tertiary-interactive;*/ + /* Once Firefox 127 is released, we can use CSS Properties (above) instead of the following code (below) */ + background-color: var(--surface-default-inverse); + color: var(--main-inverse); + + &:hover { + background-color: var(--state-inverse-hover) !important; + } + + &:active { + background-color: var(--state-inverse-selected) !important; + } + } +`; diff --git a/src/components/navigation-profile/navigation-profile.ts b/src/components/navigation-profile/navigation-profile.ts new file mode 100644 index 0000000..a2f1071 --- /dev/null +++ b/src/components/navigation-profile/navigation-profile.ts @@ -0,0 +1,35 @@ +import { html, LitElement } from "lit"; +import { customElement } from "lit/decorators.js"; +import styles from "./navigation-profile.styles.js"; +import { Contourable } from "../../mixins/mixins.js"; + +/** + * TODO this has been removed from the FIGMA, this may become deprecated soon + * @slot - The headline text. + * @slot leading - The leading content. Typically a zeta-avatar. + * + * @figma https://www.figma.com/design/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?node-id=1075-21296&node-type=canvas&m=dev + * @storybook https://zeta-ds.web.app/web/storybook/index.html?path=/story/navigation-header--profile + */ +@customElement("zeta-navigation-profile") +export class ZetaNavigationProfile extends Contourable(LitElement) { + protected override render() { + // TODO: dropdown variant + return html` + <div class="navigation-profile"> + <div class="leading"> + <slot name="leading"></slot> + </div> + <slot></slot> + </div> + `; + } + + static styles = [super.styles ?? [], styles]; +} + +declare global { + interface HTMLElementTagNameMap { + "zeta-navigation-profile": ZetaNavigationProfile; + } +} diff --git a/src/components/navigation-rail/navigation-rail-item.styles.js b/src/components/navigation-rail/navigation-rail-item.styles.js new file mode 100644 index 0000000..3c75493 --- /dev/null +++ b/src/components/navigation-rail/navigation-rail-item.styles.js @@ -0,0 +1,38 @@ +import { css } from "lit"; + +export default css` + :host { + display: flex; + max-width: 64px; + } + slot{ + font: var(--label-small); + } + + :host(:not([disabled])[selected]) > * { + --icon-color: var(--text-default); + background-color: var(--surface-selected); + } + + + + :host > * { + display: flex; + overflow-x: hidden; + text-overflow: ellipsis; + text-align: center; + flex-direction: column; + align-items: center; + padding: var(--spacing-medium) var(--spacing-large); + color: var(--main-subtle); + font-weight: 500; + --icon-color: var(--main-subtle); + background-color: var(--surface-default); + + &:hover { + background-color: var(--surface-hover); + color: var(--main-default); + --icon-color: var(--main-default); + } + } +`; diff --git a/src/components/navigation-rail/navigation-rail-item.ts b/src/components/navigation-rail/navigation-rail-item.ts new file mode 100644 index 0000000..4ece6ae --- /dev/null +++ b/src/components/navigation-rail/navigation-rail-item.ts @@ -0,0 +1,47 @@ +import { html, LitElement } from "lit"; +import { Contourable } from "../../mixins/contour"; +import { Interactive } from "../../mixins/interactive"; +import { customElement, property } from "lit/decorators.js"; +import styles from "./navigation-rail-item.styles.js"; +import { Navigate } from "../../mixins/navigate"; +import { ifDefined } from "lit/directives/if-defined.js"; + +/** + * Defines an item in a navigation rail. + * + * @slot - The label of the navigation item. + * @slot {zeta-icon} icon - The icon of the navigation item. + * + * @figma https://www.figma.com/design/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?node-id=229-43&node-type=canvas&m=dev + * @storybook https://zeta-ds.web.app/web/storybook/index.html?path=/docs/navigation-rail--docs + */ +@customElement("zeta-navigation-rail-item") +export class ZetaNavigationRailItem extends Navigate(Contourable(Interactive(LitElement))) { + static override shadowRootOptions: ShadowRootInit = { + ...LitElement.shadowRootOptions, + mode: "open", + delegatesFocus: true + }; + + /** + * Sets the navigation item as selected. + */ + @property({ type: Boolean, reflect: true }) selected = false; + + protected render() { + return html` + <a class="interactive-target" href=${ifDefined(this.href)}> + <slot name="icon"></slot> + <slot></slot> + </a> + `; + } + + static styles = [styles, super.styles || []]; +} + +declare global { + interface HTMLElementTagNameMap { + "zeta-navigation-rail-item": ZetaNavigationRailItem; + } +} diff --git a/src/components/navigation-rail/navigation-rail.styles.js b/src/components/navigation-rail/navigation-rail.styles.js new file mode 100644 index 0000000..8b6a40a --- /dev/null +++ b/src/components/navigation-rail/navigation-rail.styles.js @@ -0,0 +1,16 @@ +import { css } from "lit"; + +export default css` + :host { + display: flex; + flex-direction: column; + width: min-content; + gap: var(--spacing-minimum); + } + + ::slotted(:not(zeta-navigation-rail-item)) { + display: none; + } + + +`; diff --git a/src/components/navigation-rail/navigation-rail.ts b/src/components/navigation-rail/navigation-rail.ts new file mode 100644 index 0000000..ea5246c --- /dev/null +++ b/src/components/navigation-rail/navigation-rail.ts @@ -0,0 +1,27 @@ +import { html, LitElement } from "lit"; +import { Contourable } from "../../mixins/contour"; +import { customElement } from "lit/decorators.js"; +import styles from "./navigation-rail.styles.js"; + +/** + * Navigation rails allow navigation between sections of an app. + * + * @slot {zeta-navigation-rail-item[]} - The navigation items. + * + * @figma https://www.figma.com/design/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?node-id=229-43&node-type=canvas&m=dev + * @storybook https://zeta-ds.web.app/web/storybook/index.html?path=/docs/navigation-rail--docs + */ +@customElement("zeta-navigation-rail") +export class ZetaNavigationRail extends Contourable(LitElement) { + protected render() { + return html`<slot id="content-slot"> </slot>`; + } + + static styles = [styles, super.styles || []]; +} + +declare global { + interface HTMLElementTagNameMap { + "zeta-navigation-rail": ZetaNavigationRail; + } +} diff --git a/src/components/pagination/pagination.styles.js b/src/components/pagination/pagination.styles.js new file mode 100644 index 0000000..33d4cc2 --- /dev/null +++ b/src/components/pagination/pagination.styles.js @@ -0,0 +1,72 @@ +import { css } from "lit"; +export default css` + .pagination { + display: inline-flex; + align-items: center; + border-radius: inherit; + gap: var(--spacing-small); + } + + button { + border-radius: inherit; + background-color: transparent; + border: none; + outline: none; + cursor: pointer; + } + + .pagination-control { + cursor: pointer; + padding: var(--spacing-small) var(--spacing-minimum); + + &:disabled { + pointer-events: none; + background-color: var(--surface-disabled); + } + + &:hover { + background-color: var(--state-default-hover); + } + + &:active { + background-color: var(--state-default-selected); + } + + &:focus-visible { + border: none; + outline: none; + box-shadow: 0 0 0 var(--border-size-medium) var(--border-primary); + } + } + + .page { + text-align: center; + padding: var(--spacing-small) var(--spacing-minimum); + font: var(--body-small); + width: var(--spacing-4xl); + color: var(--main-default); + + &:hover { + background-color: var(--state-default-hover); + } + + &:active { + background-color: var(--state-default-selected); + } + + &:focus-visible { + border: none; + outline: none; + box-shadow: 0 0 0 var(--border-size-medium) var(--border-primary); + } + + &.selected { + color: var(--main-inverse); + background-color: var(--state-inverse-selected); + } + } + zeta-icon.more { + --icon-color: var(--main-default); + --icon-size: 20px; + } +`; diff --git a/src/components/pagination/pagination.ts b/src/components/pagination/pagination.ts new file mode 100644 index 0000000..0ae25bf --- /dev/null +++ b/src/components/pagination/pagination.ts @@ -0,0 +1,137 @@ +import { customElement, property } from "lit/decorators.js"; +import styles from "./pagination.styles.js"; +import { html, LitElement } from "lit"; +import { classMap } from "lit/directives/class-map.js"; +import { Contourable } from "../../mixins/mixins.js"; +import { ZetaPageEvent } from "../../events.js"; +import "../button/icon-button/icon-button.js"; +import "../icon/icon.js"; + +//TODO(UX-1339): Rounded doesnt work + +/** + * Pagination needs a description. + * + * @event {CustomEvent<ZetaPageEvent>} ZetaPageEvent:zeta-page-change - Fired when page change. Contains a single value in details: `page: number`. + * + * @figma https://www.figma.com/design/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?node-id=229-24&node-type=canvas&m=dev + * @storybook https://zeta-ds.web.app/web/storybook/index.html?path=/docs/pagination--docs + */ + +@customElement("zeta-pagination") +export class ZetaPagination extends Contourable(LitElement) { + static styles = [super.styles || [], styles]; + + /** Total number of pages. */ + @property({ type: Number }) totalPages = 10; + + /** Number of pages on both sides of current active page. */ + @property({ type: Number }) siblingCount = 1; + + /** Current active page. */ + @property({ type: Number }) + get currentPage() { + return this.page; + } + + set currentPage(page: number) { + if (page >= this.totalPages) { + this.page = this.totalPages; + } else if (page <= 1) { + this.page = 1; + } else { + this.page = page; + } + } + + private range = (start: number, end: number) => { + const length = end - start + 1; + return Array.from({ length }, (_, idx) => idx + start); + }; + + /** + * @fires ZetaPageEvent:zeta-page-change + */ + private handlePageChange = (page: number) => { + this.currentPage = page; + this.dispatchEvent(new ZetaPageEvent({ page }).toEvent()); + }; + + private page = 1; + + private result = () => { + const leftSiblingIndex = Math.max(this.currentPage - this.siblingCount, 1); + const rightSiblingIndex = Math.min(this.currentPage + this.siblingCount, this.totalPages); + const shouldShowLeftDots = leftSiblingIndex > 2; + const shouldShowRightDots = rightSiblingIndex < this.totalPages - 2; + const firstPageIndex = 1; + const lastPageIndex = this.totalPages; + + if (this.siblingCount + 5 >= this.totalPages) { + return this.range(1, this.totalPages); + } + + if (!shouldShowLeftDots && shouldShowRightDots) { + const leftItemCount = 3 + 2 * this.siblingCount; + const leftRange = this.range(1, leftItemCount); + return [...leftRange, "dots", this.totalPages]; + } + + if (shouldShowLeftDots && !shouldShowRightDots) { + const rightItemCount = 3 + 2 * this.siblingCount; + const rightRange = this.range(this.totalPages - rightItemCount + 1, this.totalPages); + return [firstPageIndex, "dots", ...rightRange]; + } + + if (shouldShowLeftDots && shouldShowRightDots) { + const middleRange = this.range(leftSiblingIndex, rightSiblingIndex); + return [firstPageIndex, "dots", ...middleRange, "dots", lastPageIndex]; + } + + return this.range(1, this.totalPages); + }; + + getIconButton(iconName: string, pageNumber: number, disabled: boolean) { + // TODO: need to migrate away from icon button, the icon colour and size is now incorrect on the text flavour + return html`<zeta-icon-button + class=${iconName} + .disabled=${disabled} + .rounded=${this.rounded} + @click=${() => this.handlePageChange(pageNumber)} + flavor="text" + size="small" + > + ${iconName} + </zeta-icon-button>`; + } + + protected render() { + const disabledLeftControl = this.currentPage === 1; + const disabledRightControl = this.currentPage === this.totalPages; + const result = this.result(); + + return html` + <div class="pagination"> + ${this.getIconButton("first_page", 1, disabledLeftControl)} ${this.getIconButton("chevron_left", this.currentPage - 1, disabledLeftControl)} + ${result.map(page => { + const pageClass = classMap({ + selected: this.currentPage === page + }); + if (typeof page === "string") { + return html`<zeta-icon class="more">more_horizontal</zeta-icon>`; + } else { + return html` <button @click=${() => this.handlePageChange(page)} class="page ${pageClass}">${page}</button> `; + } + })} + ${this.getIconButton("chevron_right", this.currentPage + 1, disabledRightControl)} + ${this.getIconButton("last_page", this.totalPages, disabledRightControl)} + </div> + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + "zeta-pagination": ZetaPagination; + } +} diff --git a/src/components/progress-indicators/progress-bar/progress-bar.styles.js b/src/components/progress-indicators/progress-bar/progress-bar.styles.js new file mode 100644 index 0000000..069e741 --- /dev/null +++ b/src/components/progress-indicators/progress-bar/progress-bar.styles.js @@ -0,0 +1,108 @@ +import { css } from "lit"; +export default css` + :host { + display: flex; + + --thin-size: var(--spacing-small); + --medium-size: var(--spacing-large); + } + + .progress-bar { + display: flex; + flex-direction: column; + gap: var(--spacing-large); + width: 100%; + } + + .wrapper { + display: flex; + gap: var(--spacing-large); + width: 100%; + } + + label { + font: var(--title-medium); + } + + :host([size="thin"]) .wrapper { + height: var(--thin-size); + } + + :host([size="medium"]) .wrapper { + height: var(--medium-size); + } + + :host([rounded]) .wrapper { + border-radius: var(--radius-large); + } + + .track { + background-color: var(--surface); + display: flex; + position: relative; + border-radius: inherit; + flex: 1; + } + + :host([buffering]) .track, + .buffering-dot { + background-color: var(--surface-disabled); + } + + .buffering-dot { + border-radius: var(--radius-xxl); + height: 100%; + display: flex; + } + + :host([size="thin"]) .buffering-dot { + width: var(--thin-size); + } + + :host([size="medium"]) .buffering-dot { + width: var(--medium-size); + } + + .bar { + background-color: var(--surface-primary); + height: 100%; + border-radius: inherit; + } + + :host(:not([indeterminate])) .bar { + transition: width 0.5s; + } + + :host([indeterminate]) .bar { + width: 30%; + position: absolute; + animation: loading 2s linear infinite; + } + + :host([indeterminate][buffering]) .bar { + animation-play-state: paused; + } + + @keyframes loading { + 0% { + left: 0%; + right: 100%; + width: 0%; + } + 10% { + left: 0%; + right: 75%; + width: 30%; + } + 90% { + left: 75%; + right: 0%; + width: 30%; + } + 100% { + left: 100%; + right: 0%; + width: 0%; + } + } +`; diff --git a/src/components/progress-indicators/progress-bar/progress-bar.ts b/src/components/progress-indicators/progress-bar/progress-bar.ts new file mode 100644 index 0000000..15b88cf --- /dev/null +++ b/src/components/progress-indicators/progress-bar/progress-bar.ts @@ -0,0 +1,68 @@ +import { html, LitElement, nothing } from "lit"; +import { customElement, property } from "lit/decorators.js"; +import styles from "./progress-bar.styles.js"; +import { Contourable } from "../../../mixins/mixins.js"; +import { styleMap } from "lit/directives/style-map.js"; + +/** Progress indicators express an unspecified wait time or display the length of a process. + * + * @figma https://www.figma.com/design/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?node-id=229-22&node-type=canvas&m=dev + * @storybook https://zeta-ds.web.app/web/storybook/index.html?path=/docs/progress--docs + */ +@customElement("zeta-progress-bar") +export class ZetaProgressBar extends Contourable(LitElement) { + /** The size of the progress indicator. Can either be 'medium' or 'thin'. */ + @property({ type: String, reflect: true }) size: "thin" | "medium" = "medium"; + + /** Displays the indeterminate progress indicator. If set to true, any argument for 'value' will be ignored. */ + @property({ type: Boolean, reflect: true }) indeterminate?: boolean; + + /** The % complete of the process indicator. */ + @property({ type: Number }) value: number = 0; + + /** The label for the progress indicator. */ + @property({ type: String }) label?: string; + + /** + * Displays the buffering dots at the end of the progress indicator. + * + * Setting this will pause the animation if 'indeterminate' is set to true. + */ + @property({ type: Boolean, reflect: true }) buffering?: boolean; + + private getBufferingDots() { + if (this.buffering) { + return html` + <div class="buffering-dot"></div> + <div class="buffering-dot"></div> + <div class="buffering-dot"></div> + `; + } else { + return nothing; + } + } + + protected override render() { + const barStyle = styleMap({ + width: `${Math.max(0, Math.min(100, this.value))}%` + }); + + return html`<div class="progress-bar"> + ${this.label ? html`<label for="progress-indicator">${this.label}</label>` : nothing} + <div class="wrapper"> + <div class="track" id="progress=indicator"> + <div class="bar" style=${!this.indeterminate ? barStyle : nothing}></div> + </div> + ${this.getBufferingDots()} + </div> + </div>`; + } + + static styles = [super.styles ?? [], styles]; +} + +declare global { + interface HTMLElementTagNameMap { + "zeta-progress-bar": ZetaProgressBar; + } +} diff --git a/src/components/progress-indicators/progress-circle/progress-circle.styles.js b/src/components/progress-indicators/progress-circle/progress-circle.styles.js new file mode 100644 index 0000000..4cc57b2 --- /dev/null +++ b/src/components/progress-indicators/progress-circle/progress-circle.styles.js @@ -0,0 +1,62 @@ +import { css } from "lit"; +export default css` + :host { + display: block; + width: fit-content; + height: fit-content; + } + .cancel zeta-icon { + --icon-size: 20px; + --icon-color: var(--main-default); + } + + svg.loading { + @keyframes rotation { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } + } + + animation: rotation 1s linear infinite; + } + + .container { + display: flex; + align-items: center; + justify-content: center; + } + + .percentage { + font: var(--label-small); + color: var(--main-default); + } + + .cancel { + display: none; + justify-content: center; + align-items: center; + background-color: var(--surface-hover); + border-radius: var(--radius-full); + cursor: pointer; + } + + .uploading { + position: absolute; + display: flex; + align-items: center; + justify-content: center; + + &:hover { + .percentage { + display: none; + } + + .cancel { + display: flex; + } + } + } +`; diff --git a/src/components/progress-indicators/progress-circle/progress-circle.ts b/src/components/progress-indicators/progress-circle/progress-circle.ts new file mode 100644 index 0000000..56b35cf --- /dev/null +++ b/src/components/progress-indicators/progress-circle/progress-circle.ts @@ -0,0 +1,126 @@ +import { customElement, property } from "lit/decorators.js"; +import { Contourable } from "../../../mixins/mixins.js"; +import { html, LitElement, nothing } from "lit"; +import styles from "./progress-circle.styles.js"; +import "../../icon/icon.js"; +import { ZetaCancelUploadEvent } from "../../../events.js"; +import { styleMap } from "lit/directives/style-map.js"; + +/** Progress indicators express an unspecified wait time or display the length of a process. + * + * @event {CustomEvent<ZetaCancelUploadEvent>} ZetaCancelUploadEvent:zeta-cancel-upload - Fired when the cancel button is clicked. + * + * @figma https://www.figma.com/design/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?node-id=229-22&node-type=canvas&m=dev + * @storybook https://zeta-ds.web.app/web/storybook/index.html?path=/docs/progress--docs + */ +@customElement("zeta-progress-circle") +export class ZetaProgressCircle extends Contourable(LitElement) { + static styles = [super.styles || [], styles]; + + /** Size. */ + @property({ type: Number }) size: 24 | 36 | 40 | 48 | 64 = 64; + + /** Progress (0-100). */ + @property({ type: Number }) get progress() { + return this.progressValue; + } + + /** The type of the progress circle. */ + @property({ type: String }) type: "default" | "upload" = "default"; + + set progress(value: number) { + if (value <= 0 || !value) { + this.progressValue = 0; + return; + } + + if (value >= 100) { + this.progressValue = 100; + return; + } + + this.progressValue = value; + } + + private progressValue = 0; + private readonly strokeWidth = 3; + + private getStrokeDasharray = () => { + // circumference = 2 × π × radius + return 2 * 3.14 * (this.size / 2 - this.strokeWidth); + }; + + private getStrokeDashoffset = () => { + // circumference × ((100 - progress)/100) + return `${(this.getStrokeDasharray() * (100 - this.progress)) / 100}`; + }; + + private renderUploading = () => { + return this.type == "upload" + ? html` + <div + class="uploading" + style=${styleMap({ + width: `${this.size}px`, + height: `${this.size}px` + })} + > + ${this.size > 24 + ? html`<span + class="percentage" + style=${styleMap({ + fontSize: `${this.size / 4}px` + })} + > + ${this.progress}% + </span>` + : nothing} + <div + @click=${() => { + this.dispatchEvent(new ZetaCancelUploadEvent().toEvent()); + }} + style=${styleMap({ + padding: `${this.size / 12}px` + })} + class="cancel" + > + <zeta-icon size=${this.size / 2}>close</zeta-icon> + </div> + </div> + ` + : nothing; + }; + + protected render() { + const r = this.size / 2 - this.strokeWidth; + const cx = this.size / 2; + const cy = this.size / 2; + const trackColor = this.type == "upload" ? "var(--main-light)" : "transparent"; + + return html` + <div class="container"> + <svg width="${this.size}" height="${this.size}" viewBox="0 0 ${this.size} ${this.size}" style="transform:rotate(-90deg)"> + <circle r=${r} cx=${cx} cy=${cy} fill="transparent" stroke="${trackColor}" stroke-width="${this.strokeWidth}px"></circle> + <circle + r=${r} + cx=${cx} + cy=${cy} + stroke="var(--main-primary)" + stroke-linecap=${this.rounded ? "round" : "square"} + fill="transparent" + stroke-width="${this.strokeWidth}px" + stroke-dasharray="${this.getStrokeDasharray()}px" + stroke-dashoffset="${this.getStrokeDashoffset()}px" + ></circle> + </svg> + ${this.renderUploading()} + </div> + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + "zeta-progress-circle": ZetaProgressCircle; + } +} diff --git a/src/components/progress-indicators/progress-indicators.ts b/src/components/progress-indicators/progress-indicators.ts new file mode 100644 index 0000000..7be73a0 --- /dev/null +++ b/src/components/progress-indicators/progress-indicators.ts @@ -0,0 +1,2 @@ +export * from "./progress-bar/progress-bar.js"; +export * from "./progress-circle/progress-circle.js"; diff --git a/src/components/radio-button/radio-button-controller.ts b/src/components/radio-button/radio-button-controller.ts new file mode 100644 index 0000000..e17a3c4 --- /dev/null +++ b/src/components/radio-button/radio-button-controller.ts @@ -0,0 +1,31 @@ +import { type ReactiveController } from "lit"; +import { type ZetaRadioButton } from "./radio-button"; + +export class RadioButtonController implements ReactiveController { + constructor(private readonly host: ZetaRadioButton) {} + private rootNode: ParentNode | null = null; + + hostConnected(): void { + this.rootNode = this.host.getRootNode() as ParentNode; + this.handleChange(); + } + hostDisconnected(): void { + this.rootNode = null; + } + handleChange(): void { + if (this.host.checked) { + this.radios?.forEach(radio => { + if (radio !== this.host) { + radio.checked = false; + } + }); + } + } + get radios(): NodeListOf<ZetaRadioButton /* | HTMLInputElement*/> | undefined { + //TODO test with mixed <input type="radio"> and zeta-radio-buttons, we may need to extend the selector to include inputs + return this.rootNode?.querySelectorAll(`zeta-radio-button[name="${this.host.name}"]`); + } + //TODO selecting with the keyboard arrows should work + //TODO write ALL the tests for this, including checking what is submitted. + //TODO test if the name is not the same (only radios with the same name should be grouped) +} diff --git a/src/components/radio-button/radio-button.styles.js b/src/components/radio-button/radio-button.styles.js new file mode 100644 index 0000000..9615abc --- /dev/null +++ b/src/components/radio-button/radio-button.styles.js @@ -0,0 +1,34 @@ +import { css } from "lit"; +export default css` + :host { + background-color: transparent; + } + + label { + cursor: pointer; + width: auto !important; + height: 100% !important; + } + + .container { + border-radius: var(--radius-full) !important; + } + + *[part="icon"] { + position: absolute; + width: 10px; + height: 10px; + border-radius: var(--radius-full); + background-color: var(--main-primary); + } + + :host([checked]:not([disabled]):hover) label:hover *[part="icon"] { + background-color: var(--border-hover); + } + + :host([disabled]) { + *[part="icon"] { + background-color: var(--surface-disabled); + } + } +`; diff --git a/src/components/radio-button/radio-button.ts b/src/components/radio-button/radio-button.ts new file mode 100644 index 0000000..536d21e --- /dev/null +++ b/src/components/radio-button/radio-button.ts @@ -0,0 +1,34 @@ +import { customElement } from "lit/decorators.js"; +import { type InputType } from "../../mixins/form-field.js"; + +import { BaseToggleFormElement } from "../base-toggle-form-element.js"; +import styles from "./radio-button.styles.js"; +import { RadioButtonController } from "./radio-button-controller.js"; + +/** + * Radio buttons allow users to select one item from a set. Radio buttons can turn an option on or off. + * + * @figma https://www.figma.com/file/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?node-id=21510-54345 + * @storybook https://zeta-ds.web.app/web/storybook/?path=/docs/radio-button--docs + */ +@customElement("zeta-radio-button") +export class ZetaRadioButton extends BaseToggleFormElement { + private readonly radioButtonController = new RadioButtonController(this); + constructor() { + super(); + this.internals.role = "radio"; + this.addController(this.radioButtonController); + } + override type: InputType = "radio"; + override handleChange(event: Event): void { + this.dispatchEvent(new Event(event.type, event)); + this.radioButtonController.handleChange(); + } + static styles = [styles, super.styles]; +} + +declare global { + interface HTMLElementTagNameMap { + "zeta-radio-button": ZetaRadioButton; + } +} diff --git a/src/components/search/search.styles.js b/src/components/search/search.styles.js new file mode 100644 index 0000000..a7addee --- /dev/null +++ b/src/components/search/search.styles.js @@ -0,0 +1,135 @@ +import { css } from "lit"; +export default css` + :host { + display: flex; + align-items: center; + width: fit-content; + height: fit-content; + --icon-size: 20px; + --icon-color: var(--main-subtle); + --icon-color: var(--main-subtle); + } + + :host([disabled]) { + --icon-color: var(--main-disabled); + .contourable-target { + pointer-events: none; + background-color: var(--surface-disabled); + } + + input { + color: var(--main-subtle); + } + } + + #search-icon { + display: var(--search-icon-display, block); + } + + :host(:not([disabled])) zeta-icon.right { + --icon-color: var(--main-default); + } + + :host([round="full"][rounded]) .contourable-target { + border-radius: var(--radius-full); + } + + form { + display: flex; + flex: 1; + align-items: center; + border-radius: inherit; + height: fit-content; + box-shadow: 0 0 0 var(--border-size-small) var(--search-border-color, var(--border-default)); + + background-color: var(--surface-default); + flex-shrink: 0; + gap: var(--spacing-small); + + &:hover { + box-shadow: 0 0 0 var(--border-size-small) var(--border-hover); + } + + &:has(input:focus) { + box-shadow: 0 0 0 var(--border-size-medium) var(--border-primary); + } + } + + input[type="search"]::-webkit-search-decoration, + input[type="search"]::-webkit-search-cancel-button, + input[type="search"]::-webkit-search-results-button, + input[type="search"]::-webkit-search-results-decoration { + display: none; + } + + input { + background-color: inherit; + border: none; + outline: none; + caret-color: var(--main-primary); + color: var(--main-subtle); + flex: 1; + padding: 0; + margin: 0; + + &::placeholder { + font-size: inherit; + line-height: inherit; + } + } + + .divider { + display: flex; + width: var(--border-size-small); + background-color: var(--border-default); + } + + /* SIZE */ + :host([size="small"]) { + --icon-size: 16px; + + .contourable-target { + padding: var(--spacing-minimum-5); + } + + .divider { + height: var(--spacing-large); + } + + input { + font: var(--body-small); + } + } + + :host([size="medium"]) { + --icon-size: 20px; + + .contourable-target { + padding: var(--spacing-small); + } + + .divider { + height: var(--spacing-2xl); + } + + input { + font: var(--body-medium); + } + } + + :host([size="large"]) { + --icon-size: 24px; + + .contourable-target { + padding: var(--spacing-small); + } + + .divider { + height: var(--spacing-2xl); + } + + input { + font: var(--body-medium); + } + } +`; diff --git a/src/components/search/search.ts b/src/components/search/search.ts new file mode 100644 index 0000000..2b247be --- /dev/null +++ b/src/components/search/search.ts @@ -0,0 +1,108 @@ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +import { customElement, property, query } from "lit/decorators.js"; +import { html, LitElement, nothing } from "lit"; +import styles from "./search.styles.js"; +import { Contourable, Interactive, Size } from "../../mixins/mixins.js"; +import "../icon/icon.js"; +import { FormField, type InputType } from "../../mixins/form-field.js"; + +/** + * Supports speech recognition search on Chrome. + * + * @figma https://www.figma.com/file/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?node-id=21286-35997 + * @storybook https://zeta-ds.web.app/web/storybook/?path=/docs/search--docs + * + * @slot leading - Leading icon + */ +@customElement("zeta-search") +export class ZetaSearch extends FormField(Size(Contourable(Interactive(LitElement)))) { + type: InputType = "search"; + + static override shadowRootOptions: ShadowRootInit = { + ...LitElement.shadowRootOptions, + delegatesFocus: true, + mode: "open" + }; + + @query("input") private readonly inputEl!: HTMLElement | null; + _round: false | true | "full" = false; + + @property({ type: String, reflect: true }) + get round(): boolean | string { + return this._round; + } + set round(value: boolean | string) { + const translatedValue: boolean | "full" = `${value}`.toLowerCase() === "true" ? true : `${value}`.toLowerCase() === "full" ? "full" : false; + this.rounded = !!translatedValue; + this._round = translatedValue; + } + + override handleChange(_event: Event): void { + this.dispatchEvent(new Event(_event.type, _event)); + } + + override focus() { + this.inputEl?.focus(); + } + + override blur() { + this.inputEl?.blur(); + } + + /** Show microphone icon. */ + @property({ type: Boolean, reflect: true }) hasIcon = false; + + private resetInput = () => { + this.value = ""; + }; + + private handleSpeechRecognition = () => { + const SpeechRecognition = (<any>window).SpeechRecognition || (<any>window).webkitSpeechRecognition; + if (SpeechRecognition) { + const s = new SpeechRecognition(); + + s.onspeechend = () => { + s.stop(); + }; + + s.onresult = (e: any) => { + this.value = e.results[0][0].transcript; + }; + + s.start(); + } + }; + + private renderRightIcon = () => { + if (("SpeechRecognition" in window || "webkitSpeechRecognition" in window) && this.hasIcon) { + return html` ${this.value ? html`<div class="divider"></div>` : nothing} + <zeta-icon @click=${() => this.handleSpeechRecognition()} .rounded=${this.rounded} class="right">microphone</zeta-icon>`; + } else { + return nothing; + } + }; + + private renderCancelIcon = () => { + return this.value ? html`<zeta-icon @click=${this.resetInput} .rounded=${this.rounded}>cancel</zeta-icon>` : nothing; + }; + + protected render() { + return html` + <form class="contourable-target"> + <zeta-icon id="search-icon" .rounded=${this.rounded}>search</zeta-icon> + ${super.render()} ${this.renderCancelIcon()} ${this.renderRightIcon()} + </form> + `; + } + + static styles = [styles, super.styles || []]; +} + +declare global { + interface HTMLElementTagNameMap { + "zeta-search": ZetaSearch; + } +} diff --git a/src/components/segmented-control/segmented-control.styles.js b/src/components/segmented-control/segmented-control.styles.js new file mode 100644 index 0000000..325725d --- /dev/null +++ b/src/components/segmented-control/segmented-control.styles.js @@ -0,0 +1,33 @@ +import { css } from "lit"; + +export default css` + .indicator { + position: absolute; + transition: all 0.3s ease-in-out; + z-index: 1; + background-color: var(--surface-default); + } + + :host { + display: flex; + position: relative; + width: fit-content; + z-index: 0; + user-select: none; + } + + :host > * { + display: flex; + width: fit-content; + padding: var(--spacing-minimum); + background-color: var(--surface-disabled); + } + + ::slotted(zeta-segmented-item) { + z-index: 2; + } + + ::slotted(:not(zeta-segmented-item)) { + display: none; + } +`; diff --git a/src/components/segmented-control/segmented-control.ts b/src/components/segmented-control/segmented-control.ts new file mode 100644 index 0000000..a6aa8b4 --- /dev/null +++ b/src/components/segmented-control/segmented-control.ts @@ -0,0 +1,97 @@ +import { html, LitElement, type PropertyValues } from "lit"; +import { Contourable } from "../../mixins/contour"; +import { customElement, query, queryAssignedElements, state } from "lit/decorators.js"; +import styles from "./segmented-control.styles.js"; +import { ZetaSegmentedItem } from "./segmented-item"; +import { styleMap } from "lit/directives/style-map.js"; + +const animationDuration = 300; + +/** + * A segmented control is a linear set of two or more segments, each of which functions as a mutually exclusive button. + * Like buttons, segments can contain text or images. Segmented controls are often used to display different views. + * + * To listen for changes, add a `click` event listener to the `zeta-segmented-control` element. + * + * To set the active segment, set the `active` property on the `zeta-segmented-item` element. + * + * @slot {zeta-segmented-item[]} - The content of the segmented control. Should be a collection of `zeta-segmented-item` elements. + * + * @fimga https://www.figma.com/design/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?node-id=1046-20148&node-type=canvas&m=dev + * @storybook https://zeta-ds.web.app/web/storybook/index.html?path=/docs/segmented-control--docs + */ +@customElement("zeta-segmented-control") +export class ZetaSegmentedControl extends Contourable(LitElement) { + @queryAssignedElements() items!: NodeListOf<ZetaSegmentedItem>; + + @state() activeItem?: ZetaSegmentedItem; + @state() initComplete = false; + @query(".indicator") indicator!: HTMLDivElement; + + constructor() { + super(); + this.addEventListener("click", e => { + if (e.target instanceof ZetaSegmentedControl) return; + + this.items.forEach(item => { + const segmentedItem = this.findSegmentedItem(e.target as HTMLElement); + if (item === segmentedItem) { + this.activeItem = segmentedItem as ZetaSegmentedItem; + void new Promise(resolve => setTimeout(resolve, animationDuration)).then(() => { + item.active = true; + }); + } else { + item.active = false; + } + }); + }); + } + + private findSegmentedItem(element: HTMLElement): HTMLElement | null { + if (element instanceof ZetaSegmentedItem) { + return element; + } + if (element.parentElement) { + return this.findSegmentedItem(element.parentElement); + } + return null; + } + + protected firstUpdated(_changedProperties: PropertyValues): void { + this.items.forEach(item => { + if (item.active) { + this.activeItem = item; + } + }); + if (!this.activeItem) { + this.activeItem = this.items[0]; + } + void new Promise(resolve => setTimeout(resolve, animationDuration)).then(() => { + this.initComplete = true; + }); + } + + render() { + return html`<div> + <div + class="indicator contourable-target" + style=${styleMap({ + visibility: this.initComplete ? "visible" : "hidden", + animationDuration: `${animationDuration}ms`, + width: `${this.activeItem?.offsetWidth}px`, + transform: `translateX(${(this.activeItem?.offsetLeft ?? 0) - 4}px)`, + height: `${this.activeItem?.offsetHeight}px` + })} + ></div> + <slot id="content-slot"></slot> + </div> `; + } + + static styles = [super.styles ?? [], styles]; +} + +declare global { + interface HTMLElementTagNameMap { + "zeta-segmented-control": ZetaSegmentedControl; + } +} diff --git a/src/components/segmented-control/segmented-item.ts b/src/components/segmented-control/segmented-item.ts new file mode 100644 index 0000000..14cdb1d --- /dev/null +++ b/src/components/segmented-control/segmented-item.ts @@ -0,0 +1,31 @@ +import { html, LitElement } from "lit"; +import { Contourable } from "../../mixins/contour"; +import { customElement, property } from "lit/decorators.js"; +import styles from "./segmented.item.styles.js"; +/** + * An item within a segmented control. + * + * @slot - The content of the item. + * + * @fimga https://www.figma.com/design/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?node-id=1046-20148&node-type=canvas&m=dev + * @storybook https://zeta-ds.web.app/web/storybook/index.html?path=/docs/segmented-control--docs + */ +@customElement("zeta-segmented-item") +export class ZetaSegmentedItem extends Contourable(LitElement) { + /** + * Whether the item is active. + */ + @property({ type: Boolean, reflect: true }) active = false; + + render() { + return html`<div><slot></slot></div>`; + } + + static styles = [super.styles ?? [], styles]; +} + +declare global { + interface HTMLElementTagNameMap { + "zeta-segmented-item": ZetaSegmentedItem; + } +} diff --git a/src/components/segmented-control/segmented.item.styles.js b/src/components/segmented-control/segmented.item.styles.js new file mode 100644 index 0000000..ae931cc --- /dev/null +++ b/src/components/segmented-control/segmented.item.styles.js @@ -0,0 +1,22 @@ +import { css } from "lit"; + +export default css` + :host > * { + border: none; + display: flex; + width: fit-content; + padding: var(--spacing-minimum) var(--spacing-large); + font-weight: 500; + font-size: 14px; + color: var(--main-disabled); + --icon-color: var(--main-disabled); + cursor: pointer; + transition: color 0.3s ease-in-out; + } + + :host([active]) > * { + color: var(--main-default); + background-color: var(--surface-default); + --icon-color: var(--main-default); + } +`; diff --git a/src/components/slider/range-selector/range-selector.ts b/src/components/slider/range-selector/range-selector.ts new file mode 100644 index 0000000..3a8aaba --- /dev/null +++ b/src/components/slider/range-selector/range-selector.ts @@ -0,0 +1,174 @@ +import { html, LitElement, nothing } from "lit"; +import { customElement, property, query } from "lit/decorators.js"; +import { Contourable } from "../../../mixins/mixins.js"; +import styles from "../slider-input-field/slider-input-field.styles.js"; +import { live } from "lit/directives/live.js"; +import { ifDefined } from "lit/directives/if-defined.js"; +import { type ZetaRangeSliderEventDetail, ZetaRangeSliderEvent } from "../../../events.js"; +import "../../text-input/text-input.js"; +import "../slider.js"; +import { FormField, type InputType } from "../../../mixins/form-field.js"; + +export type ZetaRangeValues = { min: number; max: number }; + +/** + * A ranged input field using a Zeta Slider + * + * The "name" is required when in a form. + * + * @event {CustomEvent<ZetaRangeSliderEvent>} ZetaRangeSliderEvent:zeta-range-slider-change - Fired whenever value of range slider is changed. Contains 2 values in details: `min:number`, `max:number`. + * + * @figma https://www.figma.com/design/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?node-id=980-16448&m=dev + * @storybook https://zeta-ds.web.app/web/storybook/index.html?path=/docs/slider--docs + */ +@customElement("zeta-range-selector") +export class ZetaRangeSelector extends FormField(Contourable(LitElement)) { + /** The label displayed above the input. */ + @property({ type: String }) label?: string; + + /** The name given to the input field. This is required when in a form.*/ + @property({ type: String }) name: string; + + /** The initial values of the range selector */ + @property({ type: Object }) initialValues: ZetaRangeValues = { min: 10, max: 90 }; + + /** Error state. */ + @property({ type: Boolean, reflect: true }) error = false; + + /** The minimum value of the slider input field. */ + @property({ type: Number }) min: number = 0; + + /** The maximum value of the slider input field. */ + @property({ type: Number }) max: number = 100; + + /** If set, will put steps on the slider at the given increments and the slider will snap to the nearest step. */ + @property({ type: Number }) stepIncrement?: number; + + /** Disables the input field. */ + @property({ type: Boolean, reflect: true }) disabled: boolean; + + id = "hidden-range-selector-input"; + + type: InputType = "range-selector"; + + @query("input#hidden-range-selector-input") hiddenInput!: HTMLInputElement; + @query(".lower-input") lowerInput!: HTMLInputElement; + @query(".upper-input") upperInput!: HTMLInputElement; + + /** + * @listens ZetaRangeSliderEvent:zeta-range-slider-change + */ + private sliderChange = (e: ZetaRangeSliderEvent<ZetaRangeSliderEventDetail>) => { + this.initialValues = { min: e.detail.min, max: e.detail.max }; + this.updateVisibleInputs(this.initialValues); + + this.onValueUpdated(true); + }; + + /** + * @fires ZetaRangeSliderEvent:zeta-range-slider-change + */ + private onValueUpdated(sliderChange: boolean = false) { + if (this.initialValues.min != undefined && this.initialValues.max != undefined) { + this.error = this.isError(this.initialValues); + + if (!this.isError(this.initialValues)) { + this.updateHiddenInput(); + } + if (!sliderChange) { + this.dispatchEvent(new ZetaRangeSliderEvent({ min: this.initialValues.min, max: this.initialValues.max }).toEvent()); + } + } + } + + private isError(values: ZetaRangeValues): boolean { + return values.min < this.min || values.min > this.max || values.max > this.max || values.min > values.max || isNaN(values.min) || isNaN(values.max); + } + + private handleInputChange(e: Event, isLower: boolean) { + const target = e.target as HTMLInputElement; + if (isLower) { + this.initialValues.min = parseInt(target.value); + } else { + this.initialValues.max = parseInt(target.value); + } + this.onValueUpdated(); + } + + private updateVisibleInputs(values: ZetaRangeValues) { + this.lowerInput.value = values.min.toString(); + this.upperInput.value = values.max.toString(); + } + + private updateHiddenInput() { + if (this.hiddenInput) { + this.hiddenInput.value = `${this.initialValues.min}-${this.initialValues.max}`; + this.hiddenInput.dispatchEvent(new Event("input")); + } + } + + override handleChange(event: Event): void { + this.dispatchEvent(new Event(event.type, event)); + } + + protected firstUpdated() { + this.updateHiddenInput(); + } + + private getLabel() { + if (this.label) { + return html`<label for=${this.id} class="range-selector-label">${this.label}</label>`; + } else { + return nothing; + } + } + + private getInput(isLower: boolean) { + return html` + <input + aria-label=${this.label ?? "slider input"} + ?disabled=${this.disabled} + class="contourable-target ${isLower ? "lower-input" : "upper-input"}" + type="number" + min=${isLower ? this.min : live(this.initialValues.min.toString())} + max=${isLower ? live(this.initialValues.max?.toString()) : this.max} + name=${(ifDefined(this.name), isLower ? "-lower" : "-upper")} + step=${ifDefined(this.stepIncrement)} + value=${ifDefined(isLower ? live(this.initialValues.min) : live(this.initialValues.max))} + @input=${(e: Event) => this.handleInputChange(e, isLower)} + /> + `; + } + + protected override render() { + return html` + ${this.getLabel()} + <div class="slider-input-container center"> + ${super.render()} ${this.getInput(true)} + <div class="slider-container"> + <zeta-slider + type="range" + stepIncrement=${ifDefined(this.stepIncrement)} + .rounded=${this.rounded} + .disabled=${this.disabled} + lowerValue=${ifDefined(this.initialValues.min)} + upperValue=${ifDefined(this.initialValues.max)} + min=${this.min} + max=${this.max} + @zeta-range-slider-change=${this.sliderChange} + > + </zeta-slider> + </div> + ${this.getInput(false)} + </div> + `; + } + + static styles = [super.styles ?? [], styles]; +} + +declare global { + interface HTMLElementTagNameMap { + "zeta-range-selector": ZetaRangeSelector; + } +} diff --git a/src/components/slider/slider-input-field/slider-input-field.styles.js b/src/components/slider/slider-input-field/slider-input-field.styles.js new file mode 100644 index 0000000..ae707b2 --- /dev/null +++ b/src/components/slider/slider-input-field/slider-input-field.styles.js @@ -0,0 +1,84 @@ +import { css } from "lit"; +export default css` + :host { + display: flex; + flex-direction: column; + gap: var(--spacing-minimum); + } + + :host([disabled]) { + color: var(--main-disabled); + } + + .slider-input-container { + display: flex; + gap: var(--spacing-large); + } + + .slider-input-container.center { + align-items: center; + } + + label { + margin-left: var(--spacing-small); + } + + label.range-selector-label { + margin-left: 0; + margin-bottom: var(--spacing-minimum); + } + + .slider-container { + flex: 1; + display: flex; + flex-direction: column; + justify-content: space-between; + } + + .range-label-container { + display: flex; + justify-content: space-between; + padding-right: var(--spacing-minimum); + padding-left: var(--spacing-small); + padding-bottom: var(--spacing-minimum); + } + + .range-label-container > p { + margin: var(--spacing-small) 0; + line-height: var(--spacing-2xl); + } + + /*TODO refactor to shared input styles*/ + input { + width: 56px; + height: 48px; + border: var(--border-default) var(--border-size-small) solid; + text-align: center; + font: var(--body-medium); + color: var(--main-subtle); + } + + input::-webkit-outer-spin-button, + input::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; + } + + /* Firefox */ + input[type="number"] { + -moz-appearance: textfield; + } + + :host([disabled]) input { + color: var(--main-disabled); + border-color: var(--border-disabled); + background-color: var(--surface-disabled); + } + + :host([error]:not([disabled])) { + input { + background-color: var(--surface-negative-subtle); + border: var(--border-size-small) var(--border-negative) solid; + } + } +`; diff --git a/src/components/slider/slider-input-field/slider-input-field.ts b/src/components/slider/slider-input-field/slider-input-field.ts new file mode 100644 index 0000000..5e223dc --- /dev/null +++ b/src/components/slider/slider-input-field/slider-input-field.ts @@ -0,0 +1,156 @@ +import { html, LitElement, nothing } from "lit"; +import { customElement, property, query } from "lit/decorators.js"; +import { Contourable } from "../../../mixins/mixins.js"; +import styles from "./slider-input-field.styles.js"; +import { live } from "lit/directives/live.js"; +import { ifDefined } from "lit/directives/if-defined.js"; +import { type ZetaSliderEventDetail, ZetaSliderEvent } from "../../../events.js"; +import "../../text-input/text-input.js"; +import "../slider.js"; +import { FormField, type InputType } from "../../../mixins/form-field.js"; + +//TODO: min / max dont seem to change values of slider correctly. + +/** + * An input field using a Zeta Slider + * + * @event {CustomEvent<ZetaSliderEvent>} ZetaSliderEvent:zeta-slider-change - Fired whenever value of slider is changed. Contains a single entry in detail: `value:number`. + * + * @figma https://www.figma.com/design/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?node-id=875-11860&m=dev + * @storybook https://zeta-ds.web.app/web/storybook/index.html?path=/docs/slider--docs + */ +@customElement("zeta-slider-input-field") +export class ZetaSliderInputField extends FormField(Contourable(LitElement)) { + /** The label displayed above the input. */ + @property({ type: String }) label?: string; + + /** The name given to the input field. */ + @property({ type: String }) name: string; + + /** The value of the input field. */ + @property({ type: Number, reflect: true }) initialValue: number = 50; + + /** Error state. */ + @property({ type: Boolean, reflect: true }) error = false; + + /** The minimum value of the slider input field. */ + @property({ type: Number }) min: number = 0; + + /** The maximum value of the slider input field. */ + @property({ type: Number }) max: number = 100; + + /** If set, will put steps on the slider at the given increments and the slider will snap to the nearest step. */ + @property({ type: Number }) stepIncrement?: number; + + /** Disables the input field. */ + @property({ type: Boolean, reflect: true }) disabled: boolean; + + id = "hidden-slider-input"; + + type: InputType = "slider"; + + @query("input#hidden-slider-input") hiddenInput!: HTMLInputElement; + @query("input.contourable-target") input!: HTMLInputElement; + + /** + * @listens ZetaSliderEvent:zeta-slider-change + */ + private sliderChange = (e: CustomEvent<ZetaSliderEventDetail>) => { + this.initialValue = e.detail.value; + this.updateVisibleInputs(this.initialValue); + + this.onValueUpdated(); + }; + + /** + * @fires ZetaSliderEvent:zeta-slider-change + */ + private onValueUpdated() { + if (this.initialValue != undefined) { + this.error = this.initialValue < this.min || this.initialValue > this.max || isNaN(this.initialValue); + + if (!this.error) { + this.updateHiddenInput(); + } + this.dispatchEvent(new ZetaSliderEvent({ value: this.initialValue }).toEvent()); + } + } + + private handleInputChange = (e: Event) => { + this.initialValue = parseInt((e.target as HTMLInputElement).value); + + this.onValueUpdated(); + }; + + private updateVisibleInputs(value: number) { + this.input.value = value.toString(); + } + + private updateHiddenInput() { + if (this.hiddenInput) { + this.hiddenInput.value = `${this.initialValue}`; + this.hiddenInput.dispatchEvent(new Event("input")); + } + } + + override handleChange(event: Event): void { + this.dispatchEvent(new Event(event.type, event)); + } + + protected firstUpdated() { + this.updateHiddenInput(); + } + + private getLabel() { + if (this.label) { + return html`<label for=${this.id}>${this.label}</label>`; + } else { + return nothing; + } + } + + protected override render() { + return html` + ${this.getLabel()} + <div class="slider-input-container"> + ${super.render()} + <div class="slider-container"> + <zeta-slider + stepIncrement=${ifDefined(this.stepIncrement)} + .rounded=${this.rounded} + .disabled=${this.disabled} + value=${ifDefined(this.initialValue)} + min=${this.min} + max=${this.max} + @zeta-slider-change=${this.sliderChange} + > + </zeta-slider> + <div class="range-label-container"> + <p>${this.min}</p> + <p>${this.max}</p> + </div> + </div> + <input + aria-label=${this.label ?? "slider input"} + ?disabled=${this.disabled} + class="contourable-target" + type="number" + min=${this.min} + max=${this.max} + name=${ifDefined(this.name)} + step=${ifDefined(this.stepIncrement)} + value=${ifDefined(live(this.initialValue))} + @input=${this.handleInputChange} + /> + </div> + `; + } + + static styles = [super.styles ?? [], styles]; +} + +declare global { + interface HTMLElementTagNameMap { + "zeta-slider-input-field": ZetaSliderInputField; + } +} diff --git a/src/components/slider/slider.styles.js b/src/components/slider/slider.styles.js new file mode 100644 index 0000000..887b7e2 --- /dev/null +++ b/src/components/slider/slider.styles.js @@ -0,0 +1,80 @@ +import { css } from "lit"; +export default css` + :host { + --handle-size: var(--spacing-large); + --track-height: var(--spacing-minimum); + } + + .slider { + height: var(--handle-size); + margin: 0 calc(var(--handle-size) / 2); + display: flex; + align-items: center; + position: relative; + } + + .track { + background-color: var(--surface-disabled); + height: var(--spacing-minimum); + width: 100%; + } + + .handle { + background-color: var(--main-default); + width: var(--handle-size); + height: var(--handle-size); + cursor: pointer; + position: absolute; + z-index: 5; + transition: scale 0.1s linear; + } + + :host(:not([disabled])) .handle { + &:hover:not(:active) { + scale: 1.25; + } + } + + :host([disabled]) { + .handle { + background-color: var(--main-disabled); + cursor: not-allowed; + } + + .selected-area { + display: none; + } + } + + :host([rounded]) .handle { + border-radius: var(--radius-full); + } + + .selected-area { + background-color: var(--main-default); + height: var(--track-height); + position: absolute; + z-index: 1; + left: 0; + } + + .step-container { + left: 0; + right: 0; + height: var(--track-height); + display: flex; + align-items: center; + position: absolute; + z-index: 5; + margin-left: var(--spacing-0-5); + margin-right: var(--spacing-minimum); + } + + .step { + width: var(--spacing-0-5); + height: var(--spacing-0-5); + background-color: var(--surface-default); + border-radius: var(--radius-full); + position: absolute; + } +`; diff --git a/src/components/slider/slider.ts b/src/components/slider/slider.ts new file mode 100644 index 0000000..b95b67a --- /dev/null +++ b/src/components/slider/slider.ts @@ -0,0 +1,322 @@ +import { html, LitElement, nothing } from "lit"; +import { customElement, property, query } from "lit/decorators.js"; +import styles from "./slider.styles.js"; +import { Contourable } from "../../mixins/mixins.js"; +import { styleMap } from "lit/directives/style-map.js"; +import { ZetaRangeSliderEvent, ZetaSliderEvent } from "../../events.js"; + +export * from "./slider-input-field/slider-input-field.js"; + +/** + * Sliders allow users to make selections from a range of values. + * + * @event {CustomEvent<ZetaSliderEvent>} ZetaSliderEvent:zeta-slider-change - Fired whenever value of slider is changed. Contains a single entry in details: `value:number`. + * @event {CustomEvent<ZetaRangeSliderEvent>} ZetaRangeSliderEvent:zeta-range-slider-change - Fired whenever value of range slider is changed. Contains 2 values in details: `min:number`, `max:number`. + * + * @figma https://www.figma.com/design/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?node-id=875-11860&m=dev + * @storybook https://zeta-ds.web.app/web/storybook/index.html?path=/docs/slider--docs + */ +@customElement("zeta-slider") +export class ZetaSlider extends Contourable(LitElement) { + /** Disables the slider. */ + @property({ type: Boolean, reflect: true }) disabled?: boolean; + + /** If set, will put steps on the slider at the given increments and the slider will snap to the nearest step. */ + @property({ type: Number }) stepIncrement?: number; + + /** The type of the slider. Can either be 'default' or 'range'. */ + @property({ type: String }) type: "default" | "range" = "default"; + + /** + * The value of the slider. + * + * Will have no effect if type is not 'default'. + */ + @property({ type: Number, reflect: true }) value: number = 50; + /** + * The initial value of the lower end of the slider. + * + * Will have no effect if type is not 'ranged'. + */ + @property({ type: Number, reflect: true }) lowerValue: number = 10; + + /** + * The initial value of the maximum end of the slider. + * + * Will have no effect if type is not 'ranged'. + */ + @property({ type: Number, reflect: true }) upperValue: number = 90; + + /** The minimum value of the slider. */ + @property({ type: Number }) min: number = 0; + + /** The maximum value of the slider. */ + @property({ type: Number }) max: number = 100; + + @query("#handle-l") leftHandle!: HTMLDivElement; + @query("#handle-r") rightHandle!: HTMLDivElement; + @query("#track") track!: HTMLDivElement; + @query("#selected-area") selectedArea!: HTMLDivElement; + + // Converts a pixel value to a percentage value based on the size of the track + private convertPxToPercent(val: number) { + return (val / this.track.getBoundingClientRect().width) * 100; + } + + // Takes a given value and converts it to a value between 0-100 based on the given range + private convertValueToProgress(val: number) { + const range = this.max - this.min; + const convertedVal = 100 / (range / (val - this.min)); + + return Math.max(0, Math.min(100, convertedVal)); + } + + // Gets the progress of a given handle along the track between 0-100 + private getHandleProgress(handle: HTMLDivElement) { + const trackDimensions = this.track.getBoundingClientRect(); + const handleDimensions = handle.getBoundingClientRect(); + + const handlePos = handleDimensions.x - trackDimensions.x + handleDimensions.width / 2; + + return this.convertPxToPercent(handlePos); + } + + // Returns an adjusted percentage value accounting for the width of the handle. + // Should be used to apply the styling to the 'left' attribute on the handle. + private getHandlePosition(progress: number, handle: HTMLDivElement) { + return progress - this.convertPxToPercent(handle.getBoundingClientRect().width / 2); + } + + // Gets the value of a handle relative to the minimum and maximum values + private getHandleValue(handle: HTMLDivElement) { + const progress = this.getHandleProgress(handle); + const value = this.convertProgressToValue(progress); + + return Math.round(value); + } + + // Convert a progress value (between 1-100) to a value between the given minimum and maximum values + private convertProgressToValue(progress: number) { + const range = this.max - this.min; + + return range * (progress / 100) + this.min; + } + + // Rounds a given progress value to it's nearest step + private roundToStepValue(val: number) { + // Need to convert the progress to a value in order to find it's nearest step value + val = this.convertProgressToValue(val); + const roundedVal = Math.round(val / this.stepIncrement!) * this.stepIncrement!; + + return this.convertValueToProgress(roundedVal); + } + + private setStyles() { + // TODO I don't like this but also can't move it to CSS + const selectedColor = "var(--main-primary)"; + + this.leftHandle.style.backgroundColor = selectedColor; + this.selectedArea.style.backgroundColor = selectedColor; + if (this.type == "range") { + this.rightHandle.style.backgroundColor = selectedColor; + } + } + + private removeStyles() { + this.leftHandle.style.removeProperty("background-color"); + this.selectedArea.style.removeProperty("background-color"); + if (this.type == "range") { + this.rightHandle.style.removeProperty("background-color"); + } + } + /** + * @fires ZetaSliderEvent:zeta-slider-change when the Slider value changes. + * @fires ZetaRangeSliderEvent:zeta-range-slider-change when the Range Slider value changes. + */ + private onHandleMoved = (handle: HTMLDivElement) => { + if (this.stepIncrement) this.snapHandle(handle); + + if (this.type == "default") { + this.dispatchEvent(new ZetaSliderEvent({ value: this.getHandleValue(this.leftHandle) }).toEvent()); + this.value = this.getHandleValue(this.leftHandle); + } else { + this.dispatchEvent( + new ZetaRangeSliderEvent({ + min: this.getHandleValue(this.leftHandle), + max: this.getHandleValue(this.rightHandle) + }).toEvent() + ); + this.lowerValue = this.getHandleValue(this.leftHandle); + this.upperValue = this.getHandleValue(this.rightHandle); + } + }; + + private moveHandle = (handle: HTMLDivElement, position: number) => { + handle.style.left = `${position}%`; + this.updateSelectedArea(); + }; + + private mouseDownHandler = (e: Event, handle: HTMLDivElement, offHandle: HTMLDivElement) => { + if (!this.disabled) { + e.preventDefault(); + this.setStyles(); + + handle.style.zIndex = "7"; + if (offHandle != undefined) { + offHandle.style.zIndex = "6"; + } + + const dragHandler = (e: MouseEvent) => this.dragHandler(e, handle); + + const onMouseUp = () => { + this.removeStyles(); + + this.onHandleMoved(handle); + + window.removeEventListener("mousemove", dragHandler); + window.removeEventListener("mouseup", onMouseUp); + }; + + window.addEventListener("mousemove", dragHandler); + window.addEventListener("mouseup", onMouseUp); + } + }; + + private dragHandler = (e: MouseEvent, handle: HTMLDivElement) => { + e.preventDefault(); + const handleDimensions = handle.getBoundingClientRect(); + const trackDimensions = this.track.getBoundingClientRect(); + + let lowerLimit = 0 - handleDimensions.width / 2; + let upperLimit = lowerLimit + trackDimensions.width; + + // Set the limits depending which handle is being dragged + if (this.type == "range") { + if (handle == this.leftHandle) { + const rightHandleDimensions = this.rightHandle?.getBoundingClientRect(); + upperLimit = rightHandleDimensions.left - handleDimensions.width * 1.5 - this.getBoundingClientRect().x; + } else if (handle == this.rightHandle) { + lowerLimit = this.leftHandle?.getBoundingClientRect().x - this.getBoundingClientRect().x; + } + } + + // Set the new position to be the new position of the mouse and subtract the starting position of the track and account for the handle width. + let newPos = e.clientX - trackDimensions.x - handleDimensions.width / 2; + newPos = Math.min(Math.max(lowerLimit, newPos), upperLimit); + + newPos = this.convertPxToPercent(newPos); + if (!this.stepIncrement) { + newPos = Math.round(newPos); + } + this.moveHandle(handle, newPos); + }; + + private trackClickHandler = (e: MouseEvent) => { + if (this.type == "default") { + const val = this.convertPxToPercent(e.clientX - this.leftHandle.getBoundingClientRect().width * 2); + + this.moveHandle(this.leftHandle, val); + this.onHandleMoved(this.leftHandle); + } + }; + + // Move the given handle to the nearest step + private snapHandle = (handle: HTMLDivElement) => { + // Get handle position, round to nearest step + let handleProgress = Math.round(this.getHandleProgress(handle)); + handleProgress = this.roundToStepValue(handleProgress); + + if (this.type == "range") { + // Stop one handle from going past or on top of the other + + const upperLimit = this.roundToStepValue(this.getHandleProgress(this.rightHandle)); + const lowerLimit = this.roundToStepValue(this.getHandleProgress(this.leftHandle)); + // The offset to move the handle to the nearest step + const stepAdjustment = this.convertValueToProgress(this.stepIncrement! + this.min); + + if (handle == this.leftHandle && upperLimit <= handleProgress) { + handleProgress = handleProgress - stepAdjustment; + } else if (handle == this.rightHandle && handleProgress <= lowerLimit) { + handleProgress = handleProgress + stepAdjustment; + } + } + // Adjust the handle position to account for the width of the handle + const adjustedHandlePos = this.getHandlePosition(handleProgress, handle); + + this.moveHandle(handle, adjustedHandlePos); + }; + + // Updates the size of the area between the handles + private updateSelectedArea = () => { + if (this.type == "range") { + this.selectedArea.style.left = this.leftHandle.style.left; + this.selectedArea.style.right = `${100 - this.getHandleProgress(this.rightHandle)}%`; + } else { + this.selectedArea.style.right = `${100 - this.getHandleProgress(this.leftHandle)}%`; + } + }; + + private getSteps = () => { + if (!this.stepIncrement) return nothing; + + const stepCount = Math.round((this.max - this.min) / this.stepIncrement); + const stepElements = []; + for (let i = 0; i < stepCount + 1; i++) { + // Get the value the step represents and convert it to a number between 1-100 to get it's position. + const position = this.convertValueToProgress(this.min + i * this.stepIncrement); + stepElements.push( + html`<div + class="step" + style=${styleMap({ + left: `${position}%` + })} + ></div>` + ); + } + return html`<div class="step-container">${stepElements}</div>`; + }; + + updated(): void { + if (this.type == "default") { + let initValue = this.convertValueToProgress(this.value); + + if (this.stepIncrement) { + initValue = this.roundToStepValue(initValue); + } + + this.moveHandle(this.leftHandle, this.getHandlePosition(initValue, this.leftHandle)); + } else if (this.type == "range") { + let initLowerValue = this.convertValueToProgress(this.lowerValue); + let initUpperValue = this.convertValueToProgress(this.upperValue); + + if (this.stepIncrement) { + initLowerValue = this.roundToStepValue(initLowerValue); + initUpperValue = this.roundToStepValue(initUpperValue); + } + + this.moveHandle(this.leftHandle, this.getHandlePosition(initLowerValue, this.leftHandle)); + this.moveHandle(this.rightHandle, this.getHandlePosition(initUpperValue, this.rightHandle)); + } + } + + protected override render() { + return html` + <div class="slider"> + <div id="track" class="track contourable-target" @click=${this.trackClickHandler}>${this.getSteps()}</div> + <div id="selected-area" class="selected-area contourable-target" @click=${this.trackClickHandler}></div> + <div id="handle-l" class="handle" @mousedown=${(e: MouseEvent) => this.mouseDownHandler(e, this.leftHandle, this.rightHandle)}></div> + ${this.type == "range" + ? html`<div id="handle-r" class="handle" @mousedown=${(e: MouseEvent) => this.mouseDownHandler(e, this.rightHandle, this.leftHandle)}></div>` + : nothing} + </div> + `; + } + + static styles = [super.styles ?? [], styles]; +} + +declare global { + interface HTMLElementTagNameMap { + "zeta-slider": ZetaSlider; + } +} diff --git a/src/components/snackbar/snackbar.styles.js b/src/components/snackbar/snackbar.styles.js new file mode 100644 index 0000000..4b0a266 --- /dev/null +++ b/src/components/snackbar/snackbar.styles.js @@ -0,0 +1,119 @@ +import { css } from "lit"; + +export default css` + :host { + display: flex; + align-items: center; + justify-content: space-between; + + padding: var(--spacing-medium) var(--spacing-large); + } + + :host div { + display: flex; + align-items: center; + gap: var(--spacing-large); + } + + #closeButton { + padding: 0; + background-color: transparent; + border: none; + cursor: pointer; + } + + #action { + background-color: transparent !important; + border: none; + cursor: pointer; + color: var(--main-primary); + font: var(--label-large); + } + + :host([round="false"]) { + border-radius: var(--radius-none); + } + + :host([round="true"]) { + border-radius: var(--radius-minimal); + } + + :host([round="full"]) { + border-radius: var(--radius-full); + } + + :host([status="default"]) { + background-color: var(--surface-default-inverse); + color: var(--main-inverse); + } + + :host([status="default"]) ::slotted([slot="icon"]), + :host([status="default"]) #closeIcon { + --icon-color: var(--main-inverse); + } + + :host([status="positive"]) { + background-color: var(--surface-positive-subtle); + color: var(--main-default); + } + + :host([status="positive"]) ::slotted([slot="icon"]) { + --icon-color: var(--main-positive); + } + + :host([status="positive"]) #action { + color: var(--main-default); + } + + :host([status="info"]) { + background-color: var(--surface-info-subtle); + color: var(--main-default); + } + + :host([status="info"]) ::slotted([slot="icon"]) { + --icon-color: var(--main-info); + } + + :host([status="info"]) #action { + color: var(--main-default); + } + + :host([status="warning"]) { + background-color: var(--surface-warning-subtle); + color: var(--main-default); + } + + :host([status="warning"]) ::slotted([slot="icon"]) { + --icon-color: var(--main-warning); + } + + :host([status="warning"]) #action { + color: var(--main-default); + } + + :host([status="negative"]) { + background-color: var(--surface-negative-subtle); + color: var(--main-default); + } + + :host([status="negative"]) ::slotted([slot="icon"]) { + --icon-color: var(--main-negative); + } + + :host([status="negative"]) #action { + color: var(--main-default); + } + + :host([status="view"]) { + background-color: var(--surface-primary-subtle); + color: var(--main-default); + } + + :host([status="view"]) ::slotted([slot="icon"]) { + --icon-color: var(--main-primary); + } + + :host([status="view"]) #action { + color: var(--main-default); + } +`; diff --git a/src/components/snackbar/snackbar.ts b/src/components/snackbar/snackbar.ts new file mode 100644 index 0000000..29bb7b9 --- /dev/null +++ b/src/components/snackbar/snackbar.ts @@ -0,0 +1,90 @@ +import { html, LitElement, nothing } from "lit"; +import { customElement, property } from "lit/decorators.js"; +import styles from "./snackbar.styles.js"; +import "../icon/icon.js"; +import "../button/button.js"; +import { Interactive } from "../../mixins/interactive.js"; + +/** + * Snackbars provide brief messages about app processes at the bottom of the screen. + * Contextual snackbars provide brief messages in relation to an action that has been taken by the user. + * + * @slot - The text of the snackbar. + * @slot icon - The icon of the snackbar. + * + * @figma https://www.figma.com/design/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?node-id=229-13&node-type=canvas&m=dev + * @storybook https://zeta-ds.web.app/web/storybook/index.html?path=/docs/snackbar--docs + */ +@customElement("zeta-snackbar") +export class ZetaSnackbar extends Interactive(LitElement) { + _round: boolean | "full" = "full"; + /** + * The border radius of the snackbar. Used in place of rounded prop. + * + * `"full"` + * @default "full" + */ + @property({ type: String, reflect: true }) + get round(): boolean | "full" { + return this._round; + } + set round(value: boolean | "full") { + const translatedValue: boolean | "full" = `${value}`.toLowerCase() === "true" ? true : `${value}`.toLowerCase() === "full" ? "full" : false; + this._rounded = !!translatedValue; + this._round = translatedValue; + } + + /** + * @internal + */ + _rounded: boolean = false; + + /** + * Status of the component. + */ + @property({ type: String, reflect: true }) status: "default" | "positive" | "info" | "warning" | "negative" | "view" = "default"; + + /** + * Whether the snackbar has a close action. + */ + @property({ type: Boolean }) hasCloseAction: boolean = false; + + /** + * Label of the action. + */ + @property({ type: String }) actionLabel?: string; + + /** + * Function to call when the action is clicked. + * @type {Function} + * @default () => {} + */ + @property() actionClick?: () => void; + + render() { + return html` + <div> + <slot name="icon"></slot> + <slot></slot> + </div> + <div> + ${this.actionLabel && this.actionClick ? html` <button id="action" @click=${this.actionClick}>${this.actionLabel}</button> ` : nothing} + ${this.hasCloseAction + ? html` + <button id="closeButton" @click=${() => this.remove()}> + <zeta-icon id="closeIcon" .rounded=${this._rounded}>close</zeta-icon> + </button> + ` + : nothing} + </div> + `; + } + + static styles = [super.styles ?? [], styles]; +} + +declare global { + interface HTMLElementTagNameMap { + "zeta-snackbar": ZetaSnackbar; + } +} diff --git a/src/components/stepper-input/stepper-input.styles.js b/src/components/stepper-input/stepper-input.styles.js new file mode 100644 index 0000000..c94c177 --- /dev/null +++ b/src/components/stepper-input/stepper-input.styles.js @@ -0,0 +1,60 @@ +import { css } from "lit"; +export default css` + input::-webkit-outer-spin-button, + input::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; + } + + input[type="number"] { + -moz-appearance: textfield; + } + + :host([size="large"]) { + .input-container { + padding: var(--spacing-medium); + } + } + + .container { + display: flex; + gap: var(--spacing-small); + } + + .input-container { + display: flex; + align-items: center; + justify-content: center; + padding: var(--spacing-small) var(--spacing-medium); + box-shadow: 0 0 0 var(--border-size-small) var(--border-default); + border-radius: inherit; + + &:has(input:disabled) { + background-color: var(--surface-disabled); + border: var(--border-size-small) solid transparent; + } + } + + input { + font: var(--body-medium); + width: var(--spacing-8xl); + background-color: transparent; + text-align: center; + outline: none; + border: none; + margin: 0; + padding: 0; + + &:active, + &:focus, + &:disabled { + outline: none; + border: none; + } + + &:disabled { + color: var(--main-disabled); + } + color: var(--main-default); + } +`; diff --git a/src/components/stepper-input/stepper-input.ts b/src/components/stepper-input/stepper-input.ts new file mode 100644 index 0000000..d17cde3 --- /dev/null +++ b/src/components/stepper-input/stepper-input.ts @@ -0,0 +1,98 @@ +import { customElement, property } from "lit/decorators.js"; +import { html, LitElement } from "lit"; +import { live } from "lit/directives/live.js"; +import styles from "./stepper-input.styles.js"; +import { ifDefined } from "lit/directives/if-defined.js"; +import { Contourable } from "../../mixins/mixins.js"; +import "../button/icon-button/icon-button.js"; +import "../icon/icon.js"; + +//TODO: Disable buttons when at min or max +//TODO: disabled prop changes size of box +//TODO: add FormField mixin + +/** ZetaStepperInput web component. + * A stepper input, also called numeric stepper, is a common UI element that allows users to input a number or value simply by clicking the plus and minus buttons. + * + * @figma https://www.figma.com/file/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?node-id=21529-9963 + * @storybook https://zeta-ds.web.app/web/storybook/?path=/docs/stepper-input--docs + */ +@customElement("zeta-stepper-input") +export class ZetaStepperInput extends Contourable(LitElement) { + static styles = [super.styles || [], styles]; + + @property({ type: Number }) min?: number; + @property({ type: Number }) max?: number; + @property({ type: Boolean }) disabled: boolean = false; //TODO: Use interactive, check styles beforehand + @property({ reflect: true }) size: "medium" | "large" = "medium"; + @property({ type: Number }) get value() { + return this.inputValue; + } + + set value(value: number) { + const valueToNumber = Number(value); + if (isNaN(valueToNumber) || valueToNumber === undefined) { + this.inputValue = 0; + } else if (this.max && valueToNumber >= this.max) { + this.inputValue = this.max; + } else if (this.min !== undefined && valueToNumber <= this.min) { + this.inputValue = this.min; + } else { + this.inputValue = valueToNumber; + } + } + + private handleOnChange = (value: number) => { + if (this.max && value >= this.max) { + this.value = this.max; + } else if (this.min && value <= this.min) { + this.value = this.min; + } else { + this.value = value; + } + + this.requestUpdate(); + }; + + private inputValue = 0; + + protected render() { + return html` + <div class="container"> + <zeta-icon-button + .disabled=${this.disabled} + .rounded=${this.rounded} + size=${this.size} + flavor="outline-subtle" + @click=${() => (this.value = this.value - 1)} + >remove</zeta-icon-button + > + <div class="input-container"> + <input + id=${this.id} + min=${ifDefined(this.min?.toString())} + max=${ifDefined(this.max?.toString())} + type="number" + @change=${(e: Event) => this.handleOnChange(Number((e.currentTarget as HTMLInputElement).value))} + .value=${live(this.value.toString())} + .disabled=${this.disabled} + /> + </div> + <zeta-icon-button + .disabled=${this.disabled} + .rounded=${this.rounded} + size=${this.size} + flavor="outline-subtle" + @click=${() => (this.value = this.value + 1)} + >add</zeta-icon-button + > + </div> + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + "zeta-stepper-input": ZetaStepperInput; + } +} diff --git a/src/components/stepper/stepper.styles.js b/src/components/stepper/stepper.styles.js new file mode 100644 index 0000000..89781da --- /dev/null +++ b/src/components/stepper/stepper.styles.js @@ -0,0 +1,186 @@ +import { css } from "lit"; +export default css` + .steps { + list-style-type: none; + margin: 0; + padding: 0; + display: flex; + width: fit-content; + } + + li { + display: flex; + } + + .step-container:last-of-type { + margin-bottom: 0px; + .bar { + &:after { + width: 0 !important; + display: none; + } + } + } + + .bar { + display: flex; + height: var(--spacing-4xl); + width: var(--spacing-11xl); + align-items: center; + justify-content: center; + margin-left: var(--spacing-large); + + &:after { + content: ""; + display: flex; + width: 100%; + height: var(--spacing-0-5); + border-radius: inherit; + background-color: var(--main-disabled); + } + + &:not(.show) { + &::after { + display: none !important; + } + } + + &.active { + &:after { + background-color: var(--surface-primary); + } + } + + &.completed { + &:after { + background-color: var(--surface-positive); + } + } + } + + .step { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + text-align: center; + padding: 0 var(--spacing-large); + + span { + display: flex; + } + + &:not(.active):not(.completed) { + color: var(--main-disabled); + } + + &.active { + .step-label { + color: var(--surface-primary); + } + + .step-number { + background-color: var(--surface-primary); + } + } + + &.completed { + .step-label { + color: var(--surface-positive); + } + + .step-number { + background-color: var(--surface-positive); + } + } + } + + .step-content { + display: flex; + flex-direction: column; + align-self: baseline; + + .step-title { + margin-top: var(--spacing-small); + font: var(--body-medium); + } + + .step-label { + display: none; + } + } + + .step-number { + width: var(--spacing-4xl); + height: var(--spacing-4xl); + display: flex; + align-items: center; + justify-content: center; + background-color: var(--main-disabled); + color: var(--surface-default); + font: var(--label-large); + } + + :host([rounded]) { + .step-number { + border-radius: var(--radius-full); + } + + .bar { + border-radius: var(--radius-minimal); + } + } + + :host([variant="vertical"]) { + span { + display: flex; + flex-direction: column; + } + + .steps { + flex-direction: column; + } + + .step-container { + flex-direction: column; + margin-bottom: var(--spacing-xl); + } + + .step { + flex-direction: row; + text-align: left; + padding: 0; + } + + .bar { + height: var(--spacing-4xl); + width: var(--spacing-4xl); + margin-top: var(--spacing-minimum); + margin-left: 0; + + &:after { + content: ""; + width: var(--spacing-minimum); + height: 100%; + } + } + + .step-title { + margin-top: var(--spacing-minimum); + font: var(--title-large); + } + + .step-label { + display: flex; + font: var(--body-medium); + } + + .step-number { + width: var(--spacing-4xl); + height: var(--spacing-4xl); + font: var(--label-medium); + margin-right: var(--spacing-2xl); + align-self: baseline; + } + } +`; diff --git a/src/components/stepper/stepper.ts b/src/components/stepper/stepper.ts new file mode 100644 index 0000000..cabc5e4 --- /dev/null +++ b/src/components/stepper/stepper.ts @@ -0,0 +1,75 @@ +import { customElement, property } from "lit/decorators.js"; + +import { html, LitElement, nothing } from "lit"; +import styles from "./stepper.styles.js"; +import { classMap } from "lit/directives/class-map.js"; +import { Contourable } from "../../mixins/mixins.js"; + +/** + * Steppers convey progress through numbered steps. + * + * For the steps, pass `li` elements with `data-title` and `data-label` attributes as children + * + * @figma https://www.figma.com/file/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?node-id=21529-11408 + * @figma https://www.figma.com/file/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?node-id=21529-11531 + * + * @storybook https://zeta-ds.web.app/web/storybook/?path=/docs/stepper--docs + */ +@customElement("zeta-stepper") +export class ZetaStepper extends Contourable(LitElement) { + /** Stepper direction. */ + @property({ reflect: true }) variant: "vertical" | "horizontal" = "horizontal"; + + /** Current active step. */ + @property({ type: Number }) activeStep = 0; + + /** Show bar separator. */ + @property({ type: Boolean }) bar = true; + + private renderSteps = () => { + const steps = Array.from(this.querySelectorAll<HTMLLIElement>("li")); + return html`${steps.map((step, index) => { + const classes = { + completed: index < this.activeStep, + active: this.activeStep === index + }; + + const barClass = { + show: this.bar, + completed: index < this.activeStep && this.bar, + active: this.activeStep === index && this.bar + }; + + return html` + <li class="step-container"> + <div class="step ${classMap(classes)}"> + <span> + <span class="step-number">${index + 1}</span> + <span class="bar ${classMap(barClass)}"></span> + </span> + <div class="step-content"> + ${step.childNodes[0]} ${step.dataset.label ? html`<span class="step-label">${step.dataset.label}</span>` : nothing} + <span class="step-title">${step.dataset.title}</span> + </div> + </div> + </li> + `; + })}`; + }; + + protected render() { + return html` + <ul class="steps"> + ${this.renderSteps()} + </ul> + `; + } + + static styles = [super.styles ?? [], styles]; +} + +declare global { + interface HTMLElementTagNameMap { + "zeta-stepper": ZetaStepper; + } +} diff --git a/src/components/switch/switch.styles.js b/src/components/switch/switch.styles.js new file mode 100644 index 0000000..b9ad184 --- /dev/null +++ b/src/components/switch/switch.styles.js @@ -0,0 +1,141 @@ +import { css } from "lit"; +export default css` + :host { + --_switch-width: var(--switch-width, var(--spacing-9xl)); + --_switch-height: var(--switch-height, var(--spacing-4xl)); + --_switch-thumb-size: var(--switch-thumb-size, var(--spacing-2xl)); + --_switch-icon-size: var(--switch-icon-size, var(--_switch-thumb-size)); + --_switch-padding: calc((var(--_switch-height) - var(--_switch-thumb-size)) / 2); + --_switch-icon-padding: calc((var(--_switch-height) - var(--_switch-icon-size)) / 2); + --_switch-track-width: calc(var(--_switch-width) - var(--_switch-thumb-size) - var(--_switch-padding)); + --_switch-icon-active-x: calc((var(--_switch-track-width) - var(--_switch-icon-size)) / 2 - var(--_switch-padding)); + --_switch-icon-inactive-x: calc((var(--_switch-track-width) - var(--_switch-icon-size)) / 2); + + padding: 0; + border: none; + display: flex; + width: fit-content; + gap: var(--spacing-medium); + align-items: center; + } + + :host, + div[part="track"] { + height: var(--_switch-height); + border-radius: calc(var(--_switch-height) / 2) !important; + } + + div[part="track"] { + overflow: hidden; + position: relative; + width: var(--_switch-width); + background-color: var(--switch-track-color, var(--main-disabled)); + } + + div[part="thumb"] { + width: var(--_switch-thumb-size); + height: var(--_switch-thumb-size); + border-radius: calc(var(--_switch-thumb-size) / 2); + position: absolute; + top: var(--_switch-padding); + left: var(--_switch-padding); + background-color: var(--switch-thumb-color, var(--main-inverse)); + } + + input { + visibility: hidden; + height: 0; + width: 0; + position: absolute; + margin: 0; + top: 50%; + left: 50%; + } + + zeta-icon { + position: absolute; + color: var(--switch-icon-color, var(--main-inverse)); + top: var(--_switch-icon-padding); + height: var(--_switch-icon-size); + width: var(--_switch-icon-size); + &[part="icon active"] { + /*right: 100%; //this needs to be half way of the track*/ + /*right: var(--_switch-track-icon-pos);*/ + right: calc(100% + var(--_switch-icon-active-x)); + /* right: var(--_switch-icon-active-x); */ + } + &[part="icon inactive"] { + right: var(--_switch-icon-inactive-x); + } + } + + /** This is temporary, see below */ + :host([checked]) div[part="thumb"], + :host([checked]) zeta-icon { + transform: translateX(calc(var(--_switch-width) - var(--_switch-thumb-size) - var(--_switch-padding) * 2)); + } + :host([checked]) div[part="track"] { + background-color: var(--switch-track-active-color, var(--main-primary)); + } + /* :host([checked]) zeta-icon { */ + /* transform: translateX(calc(var(--_switch-width) - var(--_switch-icon-size) - var(--_switch-icon-padding) * 2)); */ + /* transform: translateX(var(--_switch-icon-active-x)); */ + /* } */ + + /* + * This isnt working in playwright tests due to this bug + * https://github.com/microsoft/playwright/issues/31607 + / + :host([checked]) { + & div[part="thumb"] { + transform: translateX(calc(var(--_switch-width) - var(--_switch-thumb-size) - var(--_switch-padding) * 2)); + } + & div[part="track"] { + background-color: var(--switch-track-active-color, var(--main-primary)); + } + & zeta-icon { + transform: translateX(calc(var(--_switch-width) - var(--_switch-icon-size) - var(--_switch-icon-padding) * 2)); + } + } */ + + /** This is temporary, see below */ + :host([disabled]) div[part="thumb"] { + background-color: var(--switch-thumb-disabled-color, var(--main-disabled)); + } + + :host([disabled]) div[part="track"] { + background-color: var(--switch-track-disabled-color, var(--surface-disabled)); + } + + :host([disabled]) zeta-icon { + color: var(--switch-icon-disabled-color, var(--main-disabled)); + } + + /* + * This isnt working in playwright tests due to this bug + * https://github.com/microsoft/playwright/issues/31607 + :host([disabled]) { + & zeta-icon { + color: var(--switch-icon-disabled-color, var(--main-disabled)); + } + & div[part="thumb"] { + background-color: var(--switch-thumb-disabled-color, var(--main-disabled)); + } + & div[part="track"] { + background-color: var(--switch-track-disabled-color, var(--surface-disabled)); + } + } */ + + zeta-icon, + div[part="thumb"], + div[part="track"] { + transition: all 0.2s ease-out; + } + + @media (prefers-reduced-motion) { + :host, + :host * { + transition: none !important; + } + } +`; diff --git a/src/components/switch/switch.ts b/src/components/switch/switch.ts new file mode 100644 index 0000000..183ecc0 --- /dev/null +++ b/src/components/switch/switch.ts @@ -0,0 +1,101 @@ +import { html, LitElement } from "lit"; +import { customElement, property } from "lit/decorators.js"; +import styles from "./switch.styles.js"; +import { type ZetaIconName } from "@zebra-fed/zeta-icons"; +import { Contourable, Interactive } from "../../mixins/mixins.js"; +import { FormField, type InputType } from "../../mixins/form-field.js"; +import "../icon/icon.js"; +//TODO we dont have focus styles for switch +//TODO Having icons smaller than the thumb is difficult to position +/** + * Switches toggle the state of a single item ON or OFF. + * To use with icon variant, provide both activeIcon and inactiveIcon + * + * @cssproperty --switch-track-color Color of the switch track + * @cssproperty --switch-track-active-color Color of the switch track when switch is ON + * @cssproperty --switch-track-disabled-color Color of the switch track when switch is disabled + * @cssproperty --switch-icon-color Color of the activeIcon & inactiveIcon + * @cssproperty --switch-icon-disabled-color Color of the activeIcon & inactiveIcon when disabled + * @cssproperty --switch-thumb-color Color of the switch thumb + * @cssproperty --switch-thumb-disabled-color Color of the switch thumb when disabled + * @cssproperty --switch-height Height of the switch + * @cssproperty --switch-width Width of the switch + * @cssproperty --switch-thumb-size Height & Width of the switch thumb + * @cssproperty --switch-icon-size Height & Width of the activeIcon & inactiveIcon + * + * @part track - The switch track + * @part thumb - The switch thumb + * @part icon active - The active icon + * @part icon inactive - The inactive icon + * + * @figma https://www.figma.com/file/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?node-id=1153-26923 + * @storybook https://zeta-ds.web.app/web/storybook/?path=/docs/switch--docs + */ +@customElement("zeta-switch") +export class ZetaSwitch extends FormField(Interactive(Contourable(LitElement))) { + static override shadowRootOptions: ShadowRootInit = { + ...LitElement.shadowRootOptions, + mode: "open", + delegatesFocus: true + }; + constructor() { + super(); + this.internals.role = "switch"; + } + override type = "checkbox" as InputType; + + //TODO aria-checked -> on=true, off|mixed=false? + //TODO aria-readonly + + /** Icon name to display when switch is ON. */ + @property({ type: String }) activeIcon?: ZetaIconName; + + /** Icon name to display when switch is OFF. */ + @property({ type: String }) inactiveIcon?: ZetaIconName; + + override handleChange(event: Event) { + this.dispatchEvent(new Event(event.type, event)); + this.internals.ariaChecked = this.input.checked ? "true" : "false"; + } + + key(e: KeyboardEvent, type: "down" | "up") { + if (type === "up") { + if (e.key === " ") { + this.input.click(); + } + } + } + + static styles = [super.styles || [], styles]; + + protected render() { + return html` + <div + part="track" + @click=${(_e: Event) => this.input.click()} + @keydown=${(e: KeyboardEvent) => this.key(e, "down")} + @keyup=${(e: KeyboardEvent) => this.key(e, "up")} + tabindex="${this.disabled ? "-1" : this.tabIndex}" + > + <div part="thumb"></div> + ${this.activeIcon && + html` + <zeta-icon part="icon active" size=${"var(--switch-icon-size, var(--_switch-thumb-size))"} .rounded=${this.rounded}> ${this.activeIcon} </zeta-icon> + `} + ${this.inactiveIcon && + html` + <zeta-icon part="icon inactive" size=${"var(--switch-icon-size, var(--_switch-thumb-size))"} .rounded=${this.rounded}> + ${this.inactiveIcon} + </zeta-icon> + `} + </div> + ${super.render()} + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + "zeta-switch": ZetaSwitch; + } +} diff --git a/src/components/system-banner/system-banner.styles.js b/src/components/system-banner/system-banner.styles.js new file mode 100644 index 0000000..1eea2fd --- /dev/null +++ b/src/components/system-banner/system-banner.styles.js @@ -0,0 +1,60 @@ +import { css } from "lit"; +export default css` + .system-banner { + height: var(--spacing-2xl); + line-height: var(--spacing-2xl); + white-space: nowrap; + padding: var(--spacing-small) var(--spacing-large); + display: flex; + justify-content: space-between; + overflow: hidden; + > div { + display: flex; + } + > .text, + > .icon { + text-align: "center"; + font: var(--title-medium); + } + .leading, + ::slotted([slot="leading icon"]) { + padding-inline-end: var(--spacing-small); + } + + .trailing, + ::slotted([slot="trailing icon"]) { + padding-inline-start: var(--spacing-small); + justify-self: end; + } + } + + .system-banner, + :host([status="default"]) > .system-banner { + background: var(--surface-primary); + color: var(--main-inverse); + --icon-color: var(--main-inverse); + } + :host([status="positive"]) > .system-banner { + background: var(--surface-positive); + color: var(--main-inverse); + --icon-color: var(--main-inverse); + } + :host([status="warning"]) > .system-banner { + background: var(--surface-warning); + color: var(--main-default); + --icon-color: var(--main-default); + } + :host([status="negative"]) > .system-banner { + background: var(--surface-negative); + color: var(--main-inverse); + --icon-color: var(--main-inverse); + } + + :host([rounded]) > .system-banner { + border-radius: var(--radius-none) !important; + } + + .none { + display: none; + } +`; diff --git a/src/components/system-banner/system-banner.ts b/src/components/system-banner/system-banner.ts new file mode 100644 index 0000000..aeff9aa --- /dev/null +++ b/src/components/system-banner/system-banner.ts @@ -0,0 +1,62 @@ +import { html, LitElement, nothing } from "lit"; +import { customElement, property, queryAssignedElements } from "lit/decorators.js"; +import styles from "./system-banner.styles.js"; +import { Contourable } from "../../mixins/mixins.js"; +import "../../components/icon/icon.js"; + +/** + * A banner displays an important, succinct message, and provides action for users to address. + * It draws the attention to the message by displaying it at the top in various colors. + * + * @slot - Text displayed on label. + * @slot {zeta-icon} leadingIcon - Icon at leading side of text. + * @slot {zeta-icon} trailingIcon - Icon at trailing end of component. + * + * @figma https://www.figma.com/file/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?node-id=22195-43965 + * @storybook https://zeta-ds.web.app/web/storybook/?path=/docs/banner--docs + */ +@customElement("zeta-system-banner") +export class ZetaSystemBanner extends Contourable(LitElement) { + /** Type of banner.*/ + @property({ type: String, reflect: true }) status: "default" | "positive" | "warning" | "negative" = "default"; + + /** Alignment of banner.*/ + @property({ type: String, reflect: true }) align: "start" | "center" = "start"; + + /** + * Text displayed on the banner. + * + * Can also be slotted with default (unnamed) slot. + * If both are present, text prop will be displayed and slot will not + */ + @property({ type: String }) text?: string; + + @queryAssignedElements({ slot: "leadingIcon", flatten: true }) leading?: Array<Node>; + @queryAssignedElements({ slot: "trailingIcon", flatten: true }) trailing?: Array<Node>; + + static styles = [super.styles || [], styles]; + + protected render() { + const leadingIcon = html`<div class="leading icon ${this.leading && this.leading.length > 0 ? "" : "none"}"> + <slot name="leadingIcon"></slot> + </div>`; + const trailingIcon = html`<div class="trailing ${this.trailing && this.trailing.length > 0 ? "" : "none"}"> + <slot name="trailingIcon"></slot> + </div>`; + const text = html`<div class="text">${this.text ?? html`<slot></slot>`}</div>`; + + return html` + <div class="system-banner"> + <div>${this.align == "start" ? [leadingIcon, text] : nothing}</div> + <div>${this.align != "start" ? [leadingIcon, text] : nothing}</div> + ${trailingIcon} + </div> + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + "zeta-system-banner": ZetaSystemBanner; + } +} diff --git a/src/components/tab-bar/tab-bar.styles.js b/src/components/tab-bar/tab-bar.styles.js new file mode 100644 index 0000000..53a27ce --- /dev/null +++ b/src/components/tab-bar/tab-bar.styles.js @@ -0,0 +1,8 @@ +import { css } from "lit"; +export default css` + :host { + display: flex; + background-color: var(--tab-bar-background, var(--surface-default)); + justify-content: start; + } +`; diff --git a/src/components/tab-bar/tab-bar.ts b/src/components/tab-bar/tab-bar.ts new file mode 100644 index 0000000..a218cc9 --- /dev/null +++ b/src/components/tab-bar/tab-bar.ts @@ -0,0 +1,28 @@ +import { LitElement, html } from "lit"; +import { customElement } from "lit/decorators.js"; +import styles from "./tab-bar.styles.js"; + +export * from "./tab-item/tab-item.js"; + +/** + * A bar used to display a collection of zeta-tab-items. + * @cssproperty --tab-bar-background-color The background color of the tab-bar. + * @slot - The tab items displayed in the header. Should be a list of zeta-tab-item. + * + * @figma https://www.figma.com/design/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?node-id=229-18&node-type=canvas&m=dev + * @storybook https://zeta-ds.web.app/web/storybook/index.html?path=/docs/tab-bar--docs + */ +@customElement("zeta-tab-bar") +export class ZetaTabBar extends LitElement { + protected override render() { + return html` <slot></slot> `; + } + + static styles = [super.styles ?? [], styles]; +} + +declare global { + interface HTMLElementTagNameMap { + "zeta-tab-bar": ZetaTabBar; + } +} diff --git a/src/components/tab-bar/tab-item/tab-item.styles.js b/src/components/tab-bar/tab-item/tab-item.styles.js new file mode 100644 index 0000000..e8075f7 --- /dev/null +++ b/src/components/tab-bar/tab-item/tab-item.styles.js @@ -0,0 +1,26 @@ +import { css } from "lit"; +export default css` + :host { + width: min-content; + display: block; + white-space: nowrap; + background-color: var(--surface-default); + color: var(--main-subtle); + padding: var(--spacing-medium) var(--spacing-large); + font: var(--title-medium); + } + + :host(:not([disabled]):hover), + :host(:not([disabled]):active), + :host(:not([disabled])[active]) { + color: var(--main-default); + } + + :host([disabled]) { + color: var(--main-disabled); + } + + :host([active]) { + border-bottom: 2px solid var(--border-primary); + } +`; diff --git a/src/components/tab-bar/tab-item/tab-item.ts b/src/components/tab-bar/tab-item/tab-item.ts new file mode 100644 index 0000000..ad78edf --- /dev/null +++ b/src/components/tab-bar/tab-item/tab-item.ts @@ -0,0 +1,31 @@ +import { html, LitElement } from "lit"; +import { customElement, property } from "lit/decorators.js"; +import styles from "./tab-item.styles.js"; +import { Contourable, Interactive } from "../../../mixins/mixins.js"; + +/** + * A tab item to be used in a zeta-tab-bar + * + * @slot - The content of the menu item. + * + * @figma https://www.figma.com/design/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?node-id=229-18&node-type=canvas&m=dev + * @storybook https://zeta-ds.web.app/web/storybook/index.html?path=/docs/tab-bar--docs + */ +@customElement("zeta-tab-item") +export class ZetaTabItem extends Contourable(Interactive(LitElement)) { + @property({ type: Boolean, reflect: true }) active: boolean = false; + @property({ type: Boolean, reflect: true }) selected: boolean = false; + + protected override render() { + // TODO: dropdown variant + return html`<slot></slot>`; + } + + static styles = [super.styles ?? [], styles]; +} + +declare global { + interface HTMLElementTagNameMap { + "zeta-tab-item": ZetaTabItem; + } +} diff --git a/src/components/text-input/text-input.styles.js b/src/components/text-input/text-input.styles.js new file mode 100644 index 0000000..0531a3e --- /dev/null +++ b/src/components/text-input/text-input.styles.js @@ -0,0 +1,192 @@ +import { css } from "lit"; +export default css` + :host .container { + display: flex; + flex-direction: column; + gap: var(--spacing-minimum); + width: fit-content; + height: fit-content; + align-items: flex-start; + } + + .hint-text { + --icon-size: 16px; + } + + .left, + .right { + --icon-size: 20px; + } + + :host([size="small"]) .left, + :host([size="small"]) .right { + --icon-size: 16px; + } + + :host * { + border: none; + outline: none; + } + + zeta-icon { + --icon-color: var(--main-default); + } + + zeta-icon.subtle { + --icon-color: var(--main-subtle); + } + + /* ERROR */ + :host([error]:not([disabled])) .input-container { + background-color: var(--surface-negative-subtle); + box-shadow: 0 0 0 var(--border-size-medium) var(--border-negative); + } + + :host([error]:not([disabled])) .hint-text zeta-icon { + --icon-color: var(--main-negative); + } + + :host([error]:not([disabled])) .hint-text { + color: var(--main-negative); + } + + /* DISABLED */ + :host([disabled]) .input-container { + pointer-events: none; + background-color: var(--surface-disabled); + } + + :host([disabled]) zeta-icon { + --icon-color: var(--main-disabled); + } + + :host([disabled]) input, + :host([disabled]) textarea, + :host([disabled]) .affix, + :host([disabled]) .label, + :host([disabled]) .hint-text span { + color: var(--main-disabled); + } + :host([disabled]) input::placeholder, + :host([disabled]) textarea::placeholder, + :host([disabled]) .affix::placeholder, + :host([disabled]) .label::placeholder, + :host([disabled]) .hint-text span::placeholder { + color: inherit; + } + + /* SIZE */ + :host([size="small"]) .input-container:not(.text-area) { + padding: var(--spacing-small) var(--spacing-small); + } + + :host([size="small"]) input, + :host([size="small"]) .affix { + font: var(--body-small); + } + :host([size="medium"]) .input-container:not(.text-area) { + padding: var(--spacing-small) var(--spacing-medium); + } + + :host([size="medium"]) input, + :host([size="medium"]) .affix { + font: var(--body-medium); + } + + :host([size="large"]) .input-container:not(.text-area) { + padding: var(--spacing-medium); + } + + :host([size="large"]) input, + :host([size="large"]) .affix { + font: var(--body-medium); + } + + /* DEFAULT */ + .input-container { + border-radius: inherit; + display: flex; + align-items: center; + min-width: 328px; + height: fit-content; + background-color: var(--surface-default); + padding: var(--spacing-medium); + box-shadow: 0 0 0 var(--border-size-small) var(--border-default); + } + + .input-container input, + .input-container textarea { + background-color: inherit; + caret-color: var(--main-primary); + color: var(--main-default); + flex: 1; + padding: 0; + margin: 0; + font: var(--body-medium); + } + + .input-container textarea::placeholder, + .input-container input::placeholder { + color: var(--main-subtle); + } + + .input-container:hover { + box-shadow: 0 0 0 var(--border-size-small) var(--border-hover) !important; + } + + .input-container:has(input:focus), + .input-container:has(textarea:focus) { + box-shadow: 0 0 0 var(--border-size-medium) var(--border-primary) !important; + } + + input[type="date"]::-webkit-calendar-picker-indicator, + input[type="time"]::-webkit-calendar-picker-indicator { + display: none !important; + /* TODO: Firefox default date icon appears. */ + } + + .left { + margin-right: var(--spacing-small); + } + + .right { + margin-left: var(--spacing-small); + } + + .affix { + color: var(--main-subtle); + } + + .label { + font: var(--body-small); + margin-bottom: var(--spacing-minimum); + display: flex; + color: var(--main-default); + position: relative; + width: fit-content; + } + + .label.required::after { + content: "*"; + display: flex; + position: absolute; + right: var(--spacing-small); + color: var(--main-negative); + } + + .hint-text { + display: flex; + align-items: center; + column-gap: var(--spacing-minimum); + } + + .hint-text { + font: var(--body-x-small); + color: var(--main-subtle); + } + + textarea { + resize: none; + width: 100%; + } +`; diff --git a/src/components/text-input/text-input.ts b/src/components/text-input/text-input.ts new file mode 100644 index 0000000..ca9d7cb --- /dev/null +++ b/src/components/text-input/text-input.ts @@ -0,0 +1,147 @@ +import { customElement, property, query } from "lit/decorators.js"; +import { html, LitElement, nothing } from "lit"; +import styles from "./text-input.styles.js"; +import { type ZetaIconName } from "@zebra-fed/zeta-icons"; +import { classMap } from "lit/directives/class-map.js"; +import { Contourable, Interactive, Size } from "../../mixins/mixins.js"; +import "../icon/icon.js"; +import { FormField } from "../../mixins/form-field.js"; + +/** + * Text input component with icon, affix, label and hint text + * + * @figma https://www.figma.com/file/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?node-id=23116-92946 + * @storybook https://zeta-ds.web.app/web/storybook/?path=/docs/text-input--docs + */ +@customElement("zeta-text-input") +export class ZetaTextInput extends FormField(Size(Contourable(Interactive(LitElement)))) { + static override shadowRootOptions: ShadowRootInit = { delegatesFocus: true, mode: "open" }; + + static styles = [styles, super.styles ?? []]; + + constructor() { + super(); + this.internals.role = "input"; + this.role = "input"; + } + + @query("input") private readonly inputEl!: HTMLInputElement; + + /** Whether text field is in error state. */ + @property({ type: Boolean, reflect: true }) error = false; + + /** Leading icon name. */ + @property({ type: String }) leadingIcon?: ZetaIconName; + + /** Trailing icon name. */ + @property({ type: String }) trailingIcon?: ZetaIconName; + + /** Prefix text. */ + @property({ type: String }) prefix = ""; + + /** Suffix text. */ + @property({ type: String }) suffix = ""; + + /** + * Label shown above text field. + * + */ + @property() label = ""; + + /** + * Hint text shown below text field. + * + * if `error`, then `errorText` is shown instead. + */ + @property() hintText = ""; + + /** + * Error hint text + * + * Shown if `error`, replaces `hintText`. + */ + @property() errorText = ""; + + /** Type of field */ + @property({ type: String, reflect: true }) type: "text" | "textarea" | "password" | "time" | "date" = "text"; + + override focus() { + this.inputEl?.focus(); + } + + override blur() { + this.inputEl?.blur(); + } + + override handleChange(_event: Event): void { + this.dispatchEvent(new Event(_event.type, _event)); + } + + protected render() { + if (this.label) { + return html`<label class="container"> ${this.label} ${this.renderInput()} </label>`; + } else { + return html`<div class="container">${this.renderInput()}</div>`; + } + } + + private renderInput() { + const containerClass = classMap({ + "input-container": true, + "interactive-target": true, + "text-area": this.type === "textarea" + }); + return html` + <div class=${containerClass}>${this.renderLeftIcon()} ${this.renderPrefix()} ${super.render()} ${this.renderRightIcon()} ${this.renderSuffix()}</div> + ${this.error || this.hintText + ? html`<div class="hint-text"> + <zeta-icon .rounded=${this.rounded}>${this.error ? "error" : "info"}</zeta-icon> + <span id="hint-text">${this.error ? this.errorText : this.hintText}</span> + </div> ` + : nothing} + `; + } + + private renderLeftIcon() { + return this.leadingIcon && this.type === "text" && !this.toggled + ? html`<zeta-icon class="left subtle" .rounded=${this.rounded}>${this.leadingIcon}</zeta-icon> ` + : nothing; + } + + private renderRightIcon() { + return this.trailingIcon && this.type === "text" && !this.toggled + ? html`<zeta-icon class="right subtle" .rounded=${this.rounded}>${this.trailingIcon}</zeta-icon>` + : this.type === "password" || this.toggled + ? html`<zeta-icon + @click=${() => { + this.toggled = !this.toggled; + this.type = this.type === "text" ? "password" : "text"; + }} + class="right" + .rounded=${this.rounded} + > + ${this.toggled ? "visibility" : "visibility_off"} + </zeta-icon>` + : this.type === "time" || this.type === "date" + ? html`<zeta-icon @click=${() => this.inputEl.showPicker()} class="right" .rounded=${this.rounded} + >${this.type === "time" ? "clock_outline" : "calendar_3_day"}</zeta-icon + >` + : nothing; + } + + private renderPrefix() { + return this.prefix && this.type === "text" && !this.toggled ? html`<span class="left affix">${this.prefix}</span>` : nothing; + } + + private renderSuffix() { + return this.suffix && this.type === "text" && !this.toggled ? html`<span class="right affix">${this.suffix}</span>` : nothing; + } + + private toggled = false; +} + +declare global { + interface HTMLElementTagNameMap { + "zeta-text-input": ZetaTextInput; + } +} diff --git a/src/components/tooltip/tooltip.styles.js b/src/components/tooltip/tooltip.styles.js new file mode 100644 index 0000000..57807ec --- /dev/null +++ b/src/components/tooltip/tooltip.styles.js @@ -0,0 +1,58 @@ +import { css } from "lit"; +export default css` + .container { + display: flex; + align-items: center; + justify-content: center; + width: fit-content; + height: fit-content; + background: var(--main-default); + padding: var(--spacing-minimum) var(--spacing-small); + position: relative; + + @media (prefers-color-scheme: light) { + box-shadow: var(--elevation-3); + } + } + + .label { + font: var(--label-small); + color: var(--main-inverse); + } + + .point { + fill: var(--main-default); + position: absolute; + + path { + fill: inherit; + } + } + + :host([point="bottom"]) { + .point { + bottom: -4px; + } + } + + :host([point="right"]) { + .point { + transform: rotate(-90deg); + right: -6px; + } + } + + :host([point="left"]) { + .point { + transform: rotate(90deg); + left: -6px; + } + } + + :host([point="top"]) { + .point { + transform: rotate(180deg); + top: -4px; + } + } +`; diff --git a/src/components/tooltip/tooltip.ts b/src/components/tooltip/tooltip.ts new file mode 100644 index 0000000..21a65c9 --- /dev/null +++ b/src/components/tooltip/tooltip.ts @@ -0,0 +1,38 @@ +import { customElement, property } from "lit/decorators.js"; +import { html, LitElement } from "lit"; +import styles from "./tooltip.styles.js"; +import { Contourable } from "../../mixins/mixins.js"; + +/** + * Tooltips display informative text when users hover over, focus on, or tap an element. + * + * @figma https://www.figma.com/file/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?node-id=21816-222 + * @storybook https://zeta-ds.web.app/web/storybook/?path=/docs/tooltip--docs + */ +@customElement("zeta-tooltip") +export class ZetaTooltip extends Contourable(LitElement) { + /** Position of the tooltip. */ + @property({ type: String, reflect: true }) point: "left" | "right" | "top" | "bottom" = "bottom"; + + /** Text content of the tooltip. */ + @property({ type: String }) label?: string; + + static styles = [styles, super.styles ?? []]; + + protected render() { + return html` + <div class="container"> + <span class="label">${this.label}</span> + <svg class="point" xmlns="http://www.w3.org/2000/svg" width="8" height="4" viewBox="0 0 8 4" fill="none"> + <path d="${this.rounded ? "M3.29289 3.29289C3.68342 3.68342 4.31658 3.68342 4.70711 3.29289L8 0H0L3.29289 3.29289Z" : "M4 4L8 0H0L4 4Z"}" /> + </svg> + </div> + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + "zeta-tooltip": ZetaTooltip; + } +} diff --git a/src/components/top-appbar/top-appbar.styles.js b/src/components/top-appbar/top-appbar.styles.js new file mode 100644 index 0000000..a5f964a --- /dev/null +++ b/src/components/top-appbar/top-appbar.styles.js @@ -0,0 +1,35 @@ +import { css } from "lit"; + +export default css` + :host([centered]) .title { + justify-content: center; + } + + .title { + flex: 1; + display: flex; + } + + header { + display: flex; + height: fit-content; + flex-direction: column; + gap: var(--spacing-large); + background-color: var(--surface-default); + padding: var(--spacing-xl) var(--spacing-large); + font-size: 20px; + } + + .body { + gap: var(--spacing-large); + display: flex; + align-items: center; + justify-content: space-between; + } + + ::slotted(zeta-search) { + --search-icon-display: none; + --search-border-color: transparent; + width: 100%; + } +`; diff --git a/src/components/top-appbar/top-appbar.ts b/src/components/top-appbar/top-appbar.ts new file mode 100644 index 0000000..7dfae6d --- /dev/null +++ b/src/components/top-appbar/top-appbar.ts @@ -0,0 +1,49 @@ +import { html, LitElement, nothing } from "lit"; +import styles from "./top-appbar.styles.js"; +import { customElement, property } from "lit/decorators.js"; + +/** + * Top Appbars provide content and actions related to the current screen. + * + * @slot - The content of the appbar. + * @slot leading - The content to be placed at the start of the appbar. + * @slot trailing - The content to be placed at the end of the appbar. + * + * @figma https://www.figma.com/design/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?node-id=229-37&node-type=canvas&m=dev + * @storybook https://zeta-ds.web.app/web/storybook/index.html?path=/docs/appbar--docs + */ +@customElement("zeta-top-appbar") +export class ZetaTopAppbar extends LitElement { + /** Centers the content of the title. */ + @property({ type: Boolean, reflect: true }) centered = false; + + /** Places the title below the main appbar. */ + @property({ type: Boolean, reflect: true }) extended = false; + + getTitle() { + return html` <div class="title"> + <slot id="content-slot"></slot> + </div>`; + } + + protected render() { + return html` + <header> + <div class="body"> + <slot id="leading-slot" name="leading"></slot> + ${!this.extended ? this.getTitle() : nothing} + <slot id="trailing-slot" name="trailing"></slot> + </div> + ${this.extended ? this.getTitle() : nothing} + </header> + `; + } + + static styles = [styles]; +} + +declare global { + interface HTMLElementTagNameMap { + "zeta-top-appbar": ZetaTopAppbar; + } +} diff --git a/src/components/typography.ts b/src/components/typography.ts new file mode 100644 index 0000000..715fcd0 --- /dev/null +++ b/src/components/typography.ts @@ -0,0 +1,2 @@ +import "@fontsource/ibm-plex-sans/index.css"; +//TODO need to do a custom license info. diff --git a/src/components/upload-item/upload-item.styles.js b/src/components/upload-item/upload-item.styles.js new file mode 100644 index 0000000..2297efe --- /dev/null +++ b/src/components/upload-item/upload-item.styles.js @@ -0,0 +1,100 @@ +import { css } from "lit"; + +export default css` + :host { + display: flex; + min-height: 64px; + } + + :host > div { + flex: 1; + min-width: fit-content; + display: flex; + gap: var(--spacing-3); + padding: var(--spacing-2) var(--spacing-3); + user-select: none; + background-color: var(--surface-default); + + &:hover { + background-color: var(--surface-hover); + } + } + + .body { + display: flex; + flex: 1; + gap: var(--spacing-1); + flex-direction: column; + justify-content: center; + } + + .trailing { + display: flex; + align-items: center; + gap: var(--spacing-3); + } + + #cancel { + display: none; + --icon-color: var(--text-default); + cursor: pointer; + } + .title, + .subtitle { + font-weight: 500; + } + + .title { + color: var(--text-default); + font-size: 14px; + } + + .subtitle { + color: var(--text-subtle); + font-size: 12px; + } + + slot[name="leading"] { + display: flex; + align-items: center; + } + + :host([flavor="completed"]), + :host([flavor="error"]) { + #cancel { + display: flex; + } + .trailing { + margin-right: var(--spacing-3); + } + zeta-progress-circle { + display: none; + } + } + + :host([flavor="completed"]) { + > div, + > div:hover { + background-color: var(--surface-flavor-positive-subtle); + } + .subtitle { + color: var(--text-flavor-positive); + } + + .trailing { + --icon-color: var(--text-flavor-positive); + } + } + :host([flavor="error"]) { + > div, + > div:hover { + background-color: var(--surface-flavor-negative-subtle); + } + .subtitle { + color: var(--text-flavor-negative); + } + .trailing { + --icon-color: var(--text-flavor-negative); + } + } +`; diff --git a/src/components/upload-item/upload-item.ts b/src/components/upload-item/upload-item.ts new file mode 100644 index 0000000..557dd51 --- /dev/null +++ b/src/components/upload-item/upload-item.ts @@ -0,0 +1,67 @@ +import { html, LitElement } from "lit"; +import { customElement, property } from "lit/decorators.js"; +import styles from "./upload-item.styles.js"; +import { Contourable } from "../../mixins/contour.js"; +import "../icon/icon"; +import "../progress-indicators/progress-circle/progress-circle"; +import { ZetaCloseEvent } from "../../events.js"; + +/** Represents a file being uploaded. + * + * @slot - The title of the file being uploaded. + * @slot subtitle - Any extra information about the upload. + * @slot leading - The thumbnail of the file being uploaded. + * + * @event {CustomEvent<ZetaCloseEvent>} ZetaCloseEvent:close - Fired when the close icon is clicked. + * @event {CustomEvent<ZetaCancelUploadEvent>} ZetaCancelUploadEvent:cancel-upload - Fired when the cancel button inside the progress circle is clicked. + * + * @figma https://www.figma.com/design/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?node-id=229-42&node-type=canvas&m=dev + * @storybook https://zeta-ds.web.app/web/storybook/index.html?path=/docs/file-upload--docs + */ +@customElement("zeta-upload-item") +export class ZetaUploadItem extends Contourable(LitElement) { + /** The flavor of the upload item. */ + @property({ type: String }) flavor: "default" | "completed" | "error" = "default"; + + /** The progress shown on the progress circle on the upload item. */ + @property({ type: Number }) progress = 0; + + private getTrailingContent() { + switch (this.flavor) { + case "completed": + return html`<zeta-icon>check_circle</zeta-icon>`; + case "error": + return html`<zeta-icon>error</zeta-icon>`; + default: + return html` <zeta-progress-circle size="48" type="upload" progress=${this.progress}></zeta-progress-circle>`; + } + } + + private onCancelClicked() { + this.dispatchEvent(new ZetaCloseEvent().toEvent()); + } + + protected render() { + return html` + <div> + <slot name="leading"> </slot> + <div class="body"> + <div class="title"><slot></slot></div> + <div class="subtitle"><slot name="subtitle">4.6MB of 5.7MB</slot></div> + </div> + <div class="trailing"> + ${this.getTrailingContent()} + <zeta-icon id="cancel" @click=${() => this.onCancelClicked()}>close</zeta-icon> + </div> + </div> + `; + } + + static styles = [styles, super.styles || []]; +} + +declare global { + interface HTMLElementTagNameMap { + "zeta-upload-item": ZetaUploadItem; + } +} diff --git a/src/events.ts b/src/events.ts new file mode 100644 index 0000000..eef9e1d --- /dev/null +++ b/src/events.ts @@ -0,0 +1,124 @@ +abstract class ZetaEvent<T> { + abstract name: string; + detail: T; + bubbles: boolean; + composed: boolean; + cancelable: boolean; + + constructor( + detail: T, + options?: { + bubbles?: boolean; + composed?: boolean; + cancelable?: boolean; + } + ) { + this.detail = detail; + this.bubbles = options && "bubbles" in options && typeof options.bubbles === "boolean" ? options.bubbles : false; + this.composed = options && "composed" in options && typeof options.composed === "boolean" ? options.composed : false; + this.cancelable = options && "cancelable" in options && typeof options.cancelable === "boolean" ? options.cancelable : false; + } + toEvent(): CustomEvent<T> { + return new CustomEvent<T>(this.name, { + bubbles: this.bubbles, + composed: this.composed, + cancelable: this.cancelable, + detail: this.detail + }); + } +} + +export interface ZetaSliderEventDetail { + /** The value of the slider */ + value: number; +} +/** A CustomEvent factory that creates events when a standard slider is changed. */ +export class ZetaSliderEvent<T extends ZetaSliderEventDetail> extends ZetaEvent<T> { + name: string = "zeta-slider-change"; + constructor(detail: T) { + super(detail); + } +} + +/** The type of the CustomEvent fired when a ranged slider is changed. */ +export interface ZetaRangeSliderEventDetail { + /** The minimum value of the range slider. */ + min: number; + /** The maximum value of the range slider. */ + max: number; +} +/** A CustomEvent factory that creates events when a ranged slider is changed. */ +export class ZetaRangeSliderEvent<T extends ZetaRangeSliderEventDetail> extends ZetaEvent<T> { + name: string = "zeta-range-slider-change"; + constructor(detail: T) { + super(detail); + } +} + +/** The type of the CustomEvent fired when a page is changed. */ +export interface ZetaPageEventDetail { + /** The new page number. */ + page: number; +} +/** A CustomEvent factory that creates events when a ranged slider is changed. */ +export class ZetaPageEvent<T extends ZetaPageEventDetail> extends ZetaEvent<T> { + name: string = "zeta-page-change"; + constructor(detail: T) { + super(detail, { bubbles: true, composed: true }); + } +} + +/** A CustomEvent factory that creates events when a close button is clicked, e.g. on an avatar. */ +export class ZetaCloseEvent extends ZetaEvent<object> { + name: string = "close"; + constructor() { + super({}, { bubbles: true, composed: true }); + } +} + +export type ZetaPopupEventDetail = object; +/** A CustomEvent factory that creates events when a standard slider is changed. */ +export class ZetaPopupEvent extends ZetaEvent<ZetaPopupEventDetail> { + name: string = "open"; + constructor(type: "open" | "close" | "cancel") { + super({}); + this.name = type; + } +} + +/** A CustomEvent factory that creates events when the cancel button on a progess circle is clicked. */ +export class ZetaCancelUploadEvent extends ZetaEvent<object> { + name: string = "cancel-upload"; + constructor() { + super({}, { bubbles: true, composed: true }); + } +} + +export type ZetaChangeEventDetail = object; +/** A CustomEvent factory that creates events for when an input value changes. */ +export class ZetaChangeEvent extends ZetaEvent<ZetaChangeEventDetail> { + name: string = "change"; + constructor() { + super({}); + } +} + +export type ZetaInputEventDetail<S> = { + value: S; +}; +/** A CustomEvent factory that creates events for when an input value changes. */ +export class ZetaInputEvent<S, T extends ZetaInputEventDetail<S>> extends ZetaEvent<T> { + name: string = "input"; + constructor(detail: T) { + super(detail); + } +} + +// export class ZetaDropdownEvent extends ZetaEvent<ZetaDropdownEventDetail> { +// name: string = "dropdown-open"; +// name: string = "dropdown-close"; +// constructor(isOpen: boolean) { +// super({}); +// this.name = isOpen ? "zeta-modal-open" : "zeta-modal-close"; +// } +// } diff --git a/src/generated/locales/de.ts b/src/generated/locales/de.ts new file mode 100644 index 0000000..6588744 --- /dev/null +++ b/src/generated/locales/de.ts @@ -0,0 +1,17 @@ + + // Do not modify this file by hand! + // Re-generate this file by running lit-localize + + + + + /* eslint-disable no-irregular-whitespace */ + /* eslint-disable @typescript-eslint/no-explicit-any */ + + export const templates = { + 'se6ce0e7d35ca8a05': `Drop files here to upload`, +'sa192dac6f544578d': `Selection contains files with invalid types.`, +'s7008dc713aed8339': `Multiple files are not allowed.`, +'s08b05407b5565ca4': `or`, + }; + \ No newline at end of file diff --git a/src/generated/locales/en-GB.ts b/src/generated/locales/en-GB.ts new file mode 100644 index 0000000..6588744 --- /dev/null +++ b/src/generated/locales/en-GB.ts @@ -0,0 +1,17 @@ + + // Do not modify this file by hand! + // Re-generate this file by running lit-localize + + + + + /* eslint-disable no-irregular-whitespace */ + /* eslint-disable @typescript-eslint/no-explicit-any */ + + export const templates = { + 'se6ce0e7d35ca8a05': `Drop files here to upload`, +'sa192dac6f544578d': `Selection contains files with invalid types.`, +'s7008dc713aed8339': `Multiple files are not allowed.`, +'s08b05407b5565ca4': `or`, + }; + \ No newline at end of file diff --git a/src/generated/locales/en-US.ts b/src/generated/locales/en-US.ts new file mode 100644 index 0000000..6588744 --- /dev/null +++ b/src/generated/locales/en-US.ts @@ -0,0 +1,17 @@ + + // Do not modify this file by hand! + // Re-generate this file by running lit-localize + + + + + /* eslint-disable no-irregular-whitespace */ + /* eslint-disable @typescript-eslint/no-explicit-any */ + + export const templates = { + 'se6ce0e7d35ca8a05': `Drop files here to upload`, +'sa192dac6f544578d': `Selection contains files with invalid types.`, +'s7008dc713aed8339': `Multiple files are not allowed.`, +'s08b05407b5565ca4': `or`, + }; + \ No newline at end of file diff --git a/src/generated/locales/es.ts b/src/generated/locales/es.ts new file mode 100644 index 0000000..6588744 --- /dev/null +++ b/src/generated/locales/es.ts @@ -0,0 +1,17 @@ + + // Do not modify this file by hand! + // Re-generate this file by running lit-localize + + + + + /* eslint-disable no-irregular-whitespace */ + /* eslint-disable @typescript-eslint/no-explicit-any */ + + export const templates = { + 'se6ce0e7d35ca8a05': `Drop files here to upload`, +'sa192dac6f544578d': `Selection contains files with invalid types.`, +'s7008dc713aed8339': `Multiple files are not allowed.`, +'s08b05407b5565ca4': `or`, + }; + \ No newline at end of file diff --git a/src/generated/locales/fr.ts b/src/generated/locales/fr.ts new file mode 100644 index 0000000..6588744 --- /dev/null +++ b/src/generated/locales/fr.ts @@ -0,0 +1,17 @@ + + // Do not modify this file by hand! + // Re-generate this file by running lit-localize + + + + + /* eslint-disable no-irregular-whitespace */ + /* eslint-disable @typescript-eslint/no-explicit-any */ + + export const templates = { + 'se6ce0e7d35ca8a05': `Drop files here to upload`, +'sa192dac6f544578d': `Selection contains files with invalid types.`, +'s7008dc713aed8339': `Multiple files are not allowed.`, +'s08b05407b5565ca4': `or`, + }; + \ No newline at end of file diff --git a/src/generated/locales/it.ts b/src/generated/locales/it.ts new file mode 100644 index 0000000..6588744 --- /dev/null +++ b/src/generated/locales/it.ts @@ -0,0 +1,17 @@ + + // Do not modify this file by hand! + // Re-generate this file by running lit-localize + + + + + /* eslint-disable no-irregular-whitespace */ + /* eslint-disable @typescript-eslint/no-explicit-any */ + + export const templates = { + 'se6ce0e7d35ca8a05': `Drop files here to upload`, +'sa192dac6f544578d': `Selection contains files with invalid types.`, +'s7008dc713aed8339': `Multiple files are not allowed.`, +'s08b05407b5565ca4': `or`, + }; + \ No newline at end of file diff --git a/src/generated/tokens/primitives.css b/src/generated/tokens/primitives.css new file mode 100644 index 0000000..9c08259 --- /dev/null +++ b/src/generated/tokens/primitives.css @@ -0,0 +1,292 @@ +:root { + + /* COLOR */ + /* pure */ + --color-pure-0: #ffffff; + --color-pure-500: #151519; + --color-pure-1000: #151519; + /* cool */ + --color-cool-10: #f8fbff; + --color-cool-20: #f3f6fa; + --color-cool-30: #e0e3e9; + --color-cool-40: #ced2db; + --color-cool-50: #8d95a3; + --color-cool-60: #7a8190; + --color-cool-70: #545963; + --color-cool-80: #2c2f36; + --color-cool-90: #1d1e23; + --color-cool-100: #0c0d0e; + /* warm */ + --color-warm-10: #fafafa; + --color-warm-20: #f6f6f6; + --color-warm-30: #ececec; + --color-warm-40: #dedede; + --color-warm-50: #b9b9b9; + --color-warm-60: #858585; + --color-warm-70: #585858; + --color-warm-80: #313131; + --color-warm-90: #1d1e23; + --color-warm-100: #151519; + /* blue */ + --color-blue-10: #f1f8ff; + --color-blue-20: #e2f1ff; + --color-blue-30: #b7dbff; + --color-blue-40: #7ebeff; + --color-blue-50: #599fe5; + --color-blue-60: #0073e6; + --color-blue-70: #0061c2; + --color-blue-80: #004d99; + --color-blue-90: #002c58; + --color-blue-100: #101b25; + /* green */ + --color-green-10: #ecfff7; + --color-green-20: #d8ffef; + --color-green-30: #beefdb; + --color-green-40: #84dab6; + --color-green-50: #67b796; + --color-green-60: #00864f; + --color-green-70: #006d3f; + --color-green-80: #005f38; + --color-green-90: #00331e; + --color-green-100: #081711; + /* red */ + --color-red-10: #fff0f1; + --color-red-20: #ffe1e4; + --color-red-30: #ffb3bb; + --color-red-40: #f98c97; + --color-red-50: #f36170; + --color-red-60: #d70015; + --color-red-70: #b50012; + --color-red-80: #8f000e; + --color-red-90: #520008; + --color-red-100: #220f11; + /* orange */ + --color-orange-10: #fef2e2; + --color-orange-20: #ffe7c6; + --color-orange-30: #ffd292; + --color-orange-40: #ffb348; + --color-orange-50: #f5a230; + --color-orange-60: #ae6500; + --color-orange-70: #965802; + --color-orange-80: #764502; + --color-orange-90: #402600; + --color-orange-100: #1e1100; + /* purple */ + --color-purple-10: #f7f0ff; + --color-purple-20: #efe1ff; + --color-purple-30: #dcc1fb; + --color-purple-40: #cea4ff; + --color-purple-50: #9b71df; + --color-purple-60: #7e0cff; + --color-purple-70: #6400d6; + --color-purple-80: #43008f; + --color-purple-90: #260052; + --color-purple-100: #180f22; + /* yellow */ + --color-yellow-10: #fff7d4; + --color-yellow-20: #ffea89; + --color-yellow-30: #f3d961; + --color-yellow-40: #dbb91c; + --color-yellow-50: #c2a728; + --color-yellow-60: #8d7400; + --color-yellow-70: #766200; + --color-yellow-80: #564908; + --color-yellow-90: #352b00; + --color-yellow-100: #181400; + /* teal */ + --color-teal-10: #ecffff; + --color-teal-20: #d9ffff; + --color-teal-30: #bcfbfb; + --color-teal-40: #91e1e1; + --color-teal-50: #65c4c4; + --color-teal-60: #1a8080; + --color-teal-70: #017474; + --color-teal-80: #005b5b; + --color-teal-90: #003535; + --color-teal-100: #0a1616; + /* pink */ + --color-pink-10: #fff7fc; + --color-pink-20: #ffe3f5; + --color-pink-30: #ffbee7; + --color-pink-40: #ff94d8; + --color-pink-50: #ee78c3; + --color-pink-60: #d30589; + --color-pink-70: #ab006d; + --color-pink-80: #840054; + --color-pink-90: #640040; + --color-pink-100: #2e001e; + + /* RADIUS */ + --radius-s: 4px; + --radius-m: 8px; + --radius-l: 16px; + --radius-xl: 24px; + --radius-2xl: 32px; + --radius-3xl: 128px; + --radius-4xl: 360px; + + /* SPACING */ + --spacing-0: 0px; + --spacing-1: 4px; + --spacing-2: 8px; + --spacing-3: 12px; + --spacing-4: 16px; + --spacing-5: 20px; + --spacing-6: 24px; + --spacing-7: 28px; + --spacing-8: 32px; + --spacing-9: 36px; + --spacing-10: 40px; + --spacing-11: 44px; + --spacing-12: 48px; + --spacing-13: 64px; + --spacing-14: 80px; + --spacing-15: 96px; +} +@media (prefers-color-scheme: dark){ + :root { + + /* COLOR */ + /* pure */ + --color-pure-0: #151519; + --color-pure-500: #1d1e23; + --color-pure-1000: #ffffff; + /* cool */ + --color-cool-10: #0c0d0e; + --color-cool-20: #1d1e23; + --color-cool-30: #2c2f36; + --color-cool-40: #545963; + --color-cool-50: #7a8190; + --color-cool-60: #8d95a3; + --color-cool-70: #bbc1cb; + --color-cool-80: #e0e3e9; + --color-cool-90: #f3f6fa; + --color-cool-100: #f8fbff; + /* warm */ + --color-warm-10: #151519; + --color-warm-20: #1d1e23; + --color-warm-30: #313131; + --color-warm-40: #585858; + --color-warm-50: #858585; + --color-warm-60: #b9b9b9; + --color-warm-70: #dedede; + --color-warm-80: #ececec; + --color-warm-90: #f6f6f6; + --color-warm-100: #fafafa; + /* blue */ + --color-blue-10: #101b25; + --color-blue-20: #002c58; + --color-blue-30: #004d99; + --color-blue-40: #0061c2; + --color-blue-50: #0073e6; + --color-blue-60: #599fe5; + --color-blue-70: #7ebeff; + --color-blue-80: #b7dbff; + --color-blue-90: #e2f1ff; + --color-blue-100: #f1f8ff; + /* green */ + --color-green-10: #081711; + --color-green-20: #00331e; + --color-green-30: #005f38; + --color-green-40: #006d3f; + --color-green-50: #00864f; + --color-green-60: #67b796; + --color-green-70: #84dab6; + --color-green-80: #beefdb; + --color-green-90: #d8ffef; + --color-green-100: #ecfff7; + /* red */ + --color-red-10: #220f11; + --color-red-20: #520008; + --color-red-30: #8f000e; + --color-red-40: #b50012; + --color-red-50: #d70015; + --color-red-60: #f36170; + --color-red-70: #f98c97; + --color-red-80: #ffb3bb; + --color-red-90: #ffe1e4; + --color-red-100: #fff0f1; + /* orange */ + --color-orange-10: #1e1100; + --color-orange-20: #402600; + --color-orange-30: #764502; + --color-orange-40: #965802; + --color-orange-50: #ae6500; + --color-orange-60: #d78d26; + --color-orange-70: #ffb348; + --color-orange-80: #ffd292; + --color-orange-90: #ffe7c6; + --color-orange-100: #fef2e2; + /* purple */ + --color-purple-10: #180f22; + --color-purple-20: #260052; + --color-purple-30: #43008f; + --color-purple-40: #6400d6; + --color-purple-50: #7e0cff; + --color-purple-60: #9b71df; + --color-purple-70: #cea4ff; + --color-purple-80: #dcc1fb; + --color-purple-90: #efe1ff; + --color-purple-100: #f7f0ff; + /* yellow */ + --color-yellow-10: #181400; + --color-yellow-20: #352b00; + --color-yellow-30: #564908; + --color-yellow-40: #766200; + --color-yellow-50: #8d7400; + --color-yellow-60: #c2a728; + --color-yellow-70: #dbb91c; + --color-yellow-80: #f3d961; + --color-yellow-90: #ffea89; + --color-yellow-100: #fff7d4; + /* teal */ + --color-teal-10: #0a1616; + --color-teal-20: #003535; + --color-teal-30: #005b5b; + --color-teal-40: #017474; + --color-teal-50: #1a8080; + --color-teal-60: #65c4c4; + --color-teal-70: #91e1e1; + --color-teal-80: #bcfbfb; + --color-teal-90: #d9ffff; + --color-teal-100: #ecffff; + /* pink */ + --color-pink-10: #2e001e; + --color-pink-20: #640040; + --color-pink-30: #840054; + --color-pink-40: #ab006d; + --color-pink-50: #d30589; + --color-pink-60: #ee78c3; + --color-pink-70: #ff94d8; + --color-pink-80: #ffbee7; + --color-pink-90: #ffe3f5; + --color-pink-100: #fff7fc; + + /* RADIUS */ + --radius-s: 4px; + --radius-m: 8px; + --radius-l: 16px; + --radius-xl: 24px; + --radius-2xl: 32px; + --radius-3xl: 128px; + --radius-4xl: 360px; + + /* SPACING */ + --spacing-0: 0px; + --spacing-1: 4px; + --spacing-2: 8px; + --spacing-3: 12px; + --spacing-4: 16px; + --spacing-5: 20px; + --spacing-6: 24px; + --spacing-7: 28px; + --spacing-8: 32px; + --spacing-9: 36px; + --spacing-10: 40px; + --spacing-11: 44px; + --spacing-12: 48px; + --spacing-13: 64px; + --spacing-14: 80px; + --spacing-15: 96px; + } +} diff --git a/src/generated/tokens/semantics.css b/src/generated/tokens/semantics.css new file mode 100644 index 0000000..6c8aeb8 --- /dev/null +++ b/src/generated/tokens/semantics.css @@ -0,0 +1,252 @@ +:root { + + /* SURFACE */ + --surface-default: var(--color-pure-0); + --surface-default-inverse: var(--color-warm-100); + --surface-hover: var(--color-cool-20); + --surface-selected: var(--color-blue-10); + --surface-selected-hover: var(--color-blue-20); + --surface-disabled: var(--color-cool-30); + --surface-cool: var(--color-cool-10); + --surface-warm: var(--color-warm-10); + --surface-primary: var(--color-blue-60); + --surface-primary-subtle: var(--color-blue-10); + --surface-secondary: var(--color-yellow-60); + /* avatar */ + --surface-avatar-blue: var(--color-blue-80); + --surface-avatar-green: var(--color-green-60); + --surface-avatar-orange: var(--color-orange-50); + --surface-avatar-pink: var(--color-pink-80); + --surface-avatar-purple: var(--color-purple-80); + --surface-avatar-teal: var(--color-teal-80); + --surface-avatar-yellow: var(--color-yellow-50); + --surface-secondary-subtle: var(--color-yellow-10); + --surface-positive: var(--color-green-60); + --surface-positive-subtle: var(--color-green-10); + --surface-warning: var(--color-orange-60); + --surface-warning-subtle: var(--color-orange-10); + --surface-negative: var(--color-red-60); + --surface-negative-subtle: var(--color-red-10); + --surface-info: var(--color-purple-60); + --surface-info-subtle: var(--color-purple-10); + + /* BORDER */ + --border-default: var(--color-cool-40); + --border-selected: var(--color-cool-90); + --border-hover: var(--color-cool-90); + --border-subtle: var(--color-cool-30); + --border-disabled: var(--color-cool-20); + --border-pure: var(--color-pure-0); + --border-primary-main: var(--color-blue-60); + --border-primary: var(--color-blue-50); + --border-secondary: var(--color-yellow-50); + --border-positive: var(--color-green-50); + --border-warning: var(--color-orange-50); + --border-negative: var(--color-red-50); + --border-info: var(--color-purple-50); + + /* MAIN */ + --main-default: var(--color-cool-90); + --main-subtle: var(--color-cool-70); + --main-primary: var(--color-blue-60); + --main-secondary: var(--color-yellow-60); + --main-positive: var(--color-green-60); + --main-warning: var(--color-orange-60); + --main-negative: var(--color-red-60); + --main-info: var(--color-purple-60); + --main-disabled: var(--color-cool-50); + --main-light: var(--color-cool-30); + --main-inverse: var(--color-cool-20); + + /* STATE */ + /* disabled */ + --state-disabled-disabled: var(--color-cool-30); + /* default */ + --state-default-enabled: var(--color-pure-0); + --state-default-hover: var(--color-cool-20); + --state-default-selected: var(--color-blue-10); + --state-default-focus: var(--color-pure-0); + /* primary */ + --state-primary-enabled: var(--color-blue-60); + --state-primary-hover: var(--color-blue-50); + --state-primary-selected: var(--color-blue-70); + --state-primary-focus: var(--color-blue-60); + /* secondary */ + --state-secondary-enabled: var(--color-yellow-40); + --state-secondary-hover: var(--color-yellow-30); + --state-secondary-selected: var(--color-yellow-50); + --state-secondary-focus: var(--color-yellow-40); + /* negative */ + --state-negative-enabled: var(--color-red-60); + --state-negative-hover: var(--color-red-50); + --state-negative-selected: var(--color-red-70); + --state-negative-focus: var(--color-red-60); + /* info */ + --state-info-enabled: var(--color-purple-60); + --state-info-hover: var(--color-purple-50); + --state-info-selected: var(--color-purple-70); + --state-info-focus: var(--color-purple-60); + /* Inverse */ + --state-inverse-enabled: var(--color-cool-100); + --state-inverse-hover: var(--color-cool-90); + --state-inverse-selected: var(--color-cool-100); + --state-inverse-focus: var(--color-cool-100); + /* positive */ + --state-positive-enabled: var(--color-green-60); + --state-positive-hover: var(--color-green-50); + --state-positive-selected: var(--color-green-70); + --state-positive-focus: var(--color-green-60); + + /* SPACING */ + --spacing-none: var(--spacing-0); + --spacing-minimum: var(--spacing-1); + --spacing-small: var(--spacing-2); + --spacing-medium: var(--spacing-3); + --spacing-large: var(--spacing-4); + --spacing-xl: var(--spacing-5); + --spacing-2xl: var(--spacing-6); + --spacing-3xl: var(--spacing-7); + --spacing-4xl: var(--spacing-8); + --spacing-5xl: var(--spacing-9); + --spacing-6xl: var(--spacing-10); + --spacing-7xl: var(--spacing-11); + --spacing-8xl: var(--spacing-12); + --spacing-9xl: var(--spacing-13); + --spacing-10xl: var(--spacing-14); + --spacing-11xl: var(--spacing-15); + + /* RADIUS */ + --radius-none: 0px; + --radius-minimal: var(--radius-s); + --radius-rounded: var(--radius-m); + --radius-large: var(--radius-l); + --radius-xl: var(--radius-xl); + --radius-full: var(--radius-4xl); +} +@media (prefers-contrast: more){ + :root { + + /* SURFACE */ + --surface-default: var(--color-pure-0); + --surface-default-inverse: var(--color-pure-1000); + --surface-hover: var(--color-cool-20); + --surface-selected: var(--color-blue-10); + --surface-selected-hover: var(--color-blue-20); + --surface-disabled: var(--color-cool-30); + --surface-cool: var(--color-cool-10); + --surface-warm: var(--color-warm-10); + --surface-primary: var(--color-blue-80); + --surface-primary-subtle: var(--color-blue-10); + --surface-secondary: var(--color-yellow-80); + /* avatar */ + --surface-avatar-blue: var(--color-blue-80); + --surface-avatar-green: var(--color-green-60); + --surface-avatar-orange: var(--color-orange-50); + --surface-avatar-pink: var(--color-pink-80); + --surface-avatar-purple: var(--color-purple-80); + --surface-avatar-teal: var(--color-teal-80); + --surface-avatar-yellow: var(--color-yellow-50); + --surface-secondary-subtle: var(--color-yellow-10); + --surface-positive: var(--color-green-80); + --surface-positive-subtle: var(--color-green-10); + --surface-warning: var(--color-orange-80); + --surface-warning-subtle: var(--color-orange-10); + --surface-negative: var(--color-red-80); + --surface-negative-subtle: var(--color-red-10); + --surface-info: var(--color-purple-80); + --surface-info-subtle: var(--color-purple-10); + + /* BORDER */ + --border-default: var(--color-cool-100); + --border-selected: var(--color-cool-90); + --border-hover: var(--color-cool-90); + --border-subtle: var(--color-cool-80); + --border-disabled: var(--color-cool-20); + --border-pure: var(--color-pure-0); + --border-primary-main: var(--color-blue-80); + --border-primary: var(--color-blue-70); + --border-secondary: var(--color-yellow-70); + --border-positive: var(--color-green-70); + --border-warning: var(--color-orange-70); + --border-negative: var(--color-red-70); + --border-info: var(--color-purple-70); + + /* MAIN */ + --main-default: var(--color-cool-100); + --main-subtle: var(--color-cool-90); + --main-primary: var(--color-blue-80); + --main-secondary: var(--color-yellow-80); + --main-positive: var(--color-green-80); + --main-warning: var(--color-orange-80); + --main-negative: var(--color-red-80); + --main-info: var(--color-purple-80); + --main-disabled: var(--color-cool-60); + --main-light: var(--color-pure-0); + --main-inverse: var(--color-pure-0); + + /* STATE */ + /* disabled */ + --state-disabled-disabled: var(--color-cool-30); + /* default */ + --state-default-enabled: var(--color-pure-0); + --state-default-hover: var(--color-cool-20); + --state-default-selected: var(--color-blue-10); + --state-default-focus: var(--color-pure-0); + /* primary */ + --state-primary-enabled: var(--color-blue-80); + --state-primary-hover: var(--color-blue-70); + --state-primary-selected: var(--color-blue-90); + --state-primary-focus: var(--color-blue-80); + /* secondary */ + --state-secondary-enabled: var(--color-yellow-80); + --state-secondary-hover: var(--color-yellow-70); + --state-secondary-selected: var(--color-yellow-90); + --state-secondary-focus: var(--color-yellow-80); + /* negative */ + --state-negative-enabled: var(--color-red-80); + --state-negative-hover: var(--color-red-70); + --state-negative-selected: var(--color-red-90); + --state-negative-focus: var(--color-red-80); + /* info */ + --state-info-enabled: var(--color-purple-80); + --state-info-hover: var(--color-purple-70); + --state-info-selected: var(--color-purple-90); + --state-info-focus: var(--color-purple-80); + /* Inverse */ + --state-inverse-enabled: var(--color-pure-1000); + --state-inverse-hover: var(--color-cool-90); + --state-inverse-selected: var(--color-pure-1000); + --state-inverse-focus: var(--color-pure-1000); + /* positive */ + --state-positive-enabled: var(--color-green-80); + --state-positive-hover: var(--color-green-70); + --state-positive-selected: var(--color-green-90); + --state-positive-focus: var(--color-green-80); + + /* SPACING */ + --spacing-none: var(--spacing-0); + --spacing-minimum: var(--spacing-1); + --spacing-small: var(--spacing-2); + --spacing-medium: var(--spacing-3); + --spacing-large: var(--spacing-4); + --spacing-xl: var(--spacing-5); + --spacing-2xl: var(--spacing-6); + --spacing-3xl: var(--spacing-7); + --spacing-4xl: var(--spacing-8); + --spacing-5xl: var(--spacing-9); + --spacing-6xl: var(--spacing-10); + --spacing-7xl: var(--spacing-11); + --spacing-8xl: var(--spacing-12); + --spacing-9xl: var(--spacing-13); + --spacing-10xl: var(--spacing-14); + --spacing-11xl: var(--spacing-15); + + /* RADIUS */ + --radius-none: 0px; + --radius-minimal: 0px; + --radius-rounded: 0px; + --radius-large: 0px; + --radius-xl: 0px; + --radius-full: 0px; + } +} diff --git a/src/index.css b/src/index.css new file mode 100644 index 0000000..107bee3 --- /dev/null +++ b/src/index.css @@ -0,0 +1,109 @@ +@import url("./generated/tokens/primitives.css"); +@import url("./generated/tokens/semantics.css"); +/*@import url("@zebra-fed/zeta-icons/index.css");*/ +/* All tokens defined here are used in code but not from figma */ + +:root, +:host { + color: var(--main-default); + font-family: var(--type-family-regular); + + /* Colors */ + --surface-overlay: rgba(0, 0, 0, 0.3); + --surface-basic: transparent; + + --type-family-regular: "IBM Plex Sans", sans-serif; + + /* Half spacing */ + --spacing-0-5: 2px; + --spacing-1-5: 6px; + --spacing-2-5: 10px; + --spacing-3-5: 14px; + --spacing-4-5: 18px; + --spacing-20: 80px; + + /* Border size */ + --border-size-small: 1px; + --border-size-medium: 2px; + + /* Box shadows */ + --elevation-1: 0 0.5px 2px 0 rgba(96, 104, 112, 0.16), 0 0 1px 0 rgba(40, 51, 61, 0.08); + --elevation-2: 0 2px 4px 0 rgba(96, 104, 112, 0.16), 0 0 1px 0 rgba(40, 51, 61, 0.04); + --elevation-3: 0 4px 8px 0 rgba(96, 104, 112, 0.16), 0 0 2px 0 rgba(40, 51, 61, 0.04); + --elevation-4: 0 8px 16px 0 rgba(96, 104, 112, 0.16), 0 2px 4px 0 rgba(40, 51, 61, 0.04); + --elevation-5: 0 16px 24px 0 rgba(96, 104, 112, 0.16), 0 2px 8px 0 rgba(40, 51, 61, 0.04); + --elevation-6: 0 20px 32px 0 rgba(96, 104, 112, 0.24), 0 2px 8px 0 rgba(40, 51, 61, 0.08); + + /* Font Weight */ + --medium: 500; + --regular: 400; + --light: 300; + + /* Font */ + --display-large: var(--light) 3.25rem/3.75rem var(--type-family-regular); + --display-medium: var(--light) 2.75rem/3.25rem var(--type-family-regular); + --display-small: var(--light) 2.25rem/2.5rem var(--type-family-regular); + + --headline-large: var(--medium) 2rem/2.25rem var(--type-family-regular); + --headline-medium: var(--medium) 1.75rem/2rem var(--type-family-regular); + --headline-small: var(--medium) 1.5rem/1.75rem var(--type-family-regular); + + --title-large: var(--medium) 1.25rem/1.5rem var(--type-family-regular); + --title-medium: var(--medium) 1rem/1.25rem var(--type-family-regular); + --title-small: var(--medium) 0.75rem/1rem var(--type-family-regular); + + --body-large: var(--regular) 1.25rem/1.5rem var(--type-family-regular); + --body-medium: var(--regular) 1rem/1.5rem var(--type-family-regular); + --body-small: var(--regular) 0.875rem/1.125rem var(--type-family-regular); + --body-x-small: var(--regular) 0.75rem/1rem var(--type-family-regular); + + --label-large: var(--medium) 1rem/1.5rem var(--type-family-regular); + --label-medium: var(--medium) 0.875rem/1.25rem var(--type-family-regular); + --label-small: var(--medium) 0.75rem/1rem var(--type-family-regular); + --label-indicator: var(--medium) 0.75rem/0.875rem var(--type-family-regular); + + --h1: var(--headline-large); + --h2: var(--headline-medium); + --h3: var(--headline-small); + --h4: var(--title-large); + --h5: var(--title-medium); + --h6: var(--title-small); +} + +h1 { + font: var(--h1); +} + +h2 { + font: var(--h2); +} + +h3 { + font: var(--h3); +} + +h4 { + font: var(--h4); +} + +h5 { + font: var(--h5); +} + +h6 { + font: var(--h6); +} + +p { + font: var(--body-small); +} +small { + font: var(--body-x-small); +} + +@media (prefers-color-scheme: dark) { + :root { + /* Primitives */ + --surface-overlay: rgba(0, 0, 0, 0.7); + } +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..ab6ae81 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,143 @@ +import "./index.css"; +import { ZetaAccordion } from "./components/accordion/accordion.js"; +import { ZetaActionMenuButton } from "./components/action-menu/action-menu-button.js"; +import { ZetaAssistChip } from "./components/chips/assist-chip/assist-chip.js"; +import { ZetaAvatar } from "./components/avatar/avatar.js"; +import { ZetaAvatarRail } from "./components/avatar-rail/avatar-rail.js"; +import { ZetaBottomSheet } from "./components/bottom-sheets/bottom-sheet.js"; +import { ZetaBreadcrumb } from "./components/breadcrumbs/breadcrumb.js"; +import { ZetaBreadcrumbItem } from "./components/breadcrumbs/breadcrumb-item/breadcrumb-item.js"; +import { ZetaButton } from "./components/button/button.js"; +import { ZetaButtonGroup } from "./components/button-group/button-group.js"; +import { ZetaButtonGroupItem } from "./components/button-group/button-group-item/button-group-item.js"; +import { ZetaCard } from "./components/card/card.js"; +import { ZetaCardBody } from "./components/card/card-body/card-body.js"; +import { ZetaCardFooter } from "./components/card/card-footer/card-footer.js"; +import { ZetaCardHeader } from "./components/card/card-header/card-header.js"; +import { ZetaCheckbox } from "./components/checkbox/checkbox.js"; +import { ZetaDialog } from "./components/dialog/dialog.js"; +import { ZetaDropdownMenuButton } from "./components/dropdown/dropdown-menu/dropdown-menu-button.js"; +import { ZetaDropdownMenuItem } from "./components/dropdown/menu-item/dropdown-menu-item.js"; +import { ZetaDroppable } from "./components/dropdown/droppable.js"; +import { ZetaFab } from "./components/fab/fab.js"; +import { ZetaFileUpload } from "./components/file-upload/file-upload.js"; +import { ZetaFilterChip } from "./components/chips/filter-chip/filter-chip.js"; +import { ZetaGlobalHeader } from "./components/global-header/global-header.js"; +import { ZetaGridMenuItem } from "./components/grid-menu-item/grid-menu-item.js"; +import { ZetaIcon } from "./components/icon/icon.js"; +import { ZetaIconButton } from "./components/button/icon-button/icon-button.js"; +import { ZetaIconIndicator } from "./components/badges/indicators/indicators.js"; +import { ZetaIndicator } from "./components/badges/indicators/indicators.js"; +import { ZetaInPageBanner } from "./components/in-page-banner/in-page-banner.js"; +import { ZetaInputChip } from "./components/chips/input-chip/input-chip.js"; +import { ZetaLabel } from "./components/badges/label/label.js"; +import { ZetaList } from "./components/list/list.js"; +import { ZetaListItem } from "./components/list/list-item/list-item.js"; +import { ZetaNavigationBar } from "./components/navigation-bar/navigation-bar.js"; +import { ZetaNavigationDrawer } from "./components/navigation-drawer/navigation-drawer.js"; +import { ZetaNavigationDrawerFooter } from "./components/navigation-drawer/navigation-drawer-footer/navigation-drawer-footer.js"; +import { ZetaNavigationDrawerHeader } from "./components/navigation-drawer/navigation-drawer-header/navigation-drawer-header.js"; +import { ZetaNavigationDrawerItem } from "./components/navigation-drawer/navigation-drawer-item/navigation-drawer-item.js"; +import { ZetaNavigationDrawerSubItem } from "./components/navigation-drawer/navigation-drawer-sub-item/navigation-drawer-sub-item.js"; +import { ZetaNavigationProfile } from "./components/navigation-profile/navigation-profile.js"; +import { ZetaNavigationRail } from "./components/navigation-rail/navigation-rail.js"; +import { ZetaNavigationRailItem } from "./components/navigation-rail/navigation-rail-item.js"; +import { ZetaNotificationIndicator } from "./components/badges/indicators/indicators.js"; +import { ZetaPagination } from "./components/pagination/pagination.js"; +import { ZetaPriorityPill } from "./components/badges/priority-pill/priority-pill.js"; +import { ZetaProgressBar } from "./components/progress-indicators/progress-bar/progress-bar.js"; +import { ZetaProgressCircle } from "./components/progress-indicators/progress-circle/progress-circle.js"; +import { ZetaRadioButton } from "./components/radio-button/radio-button.js"; +import { ZetaRangeSelector } from "./components/slider/range-selector/range-selector.js"; +import { ZetaSearch } from "./components/search/search.js"; +import { ZetaSegmentedControl } from "./components/segmented-control/segmented-control.js"; +import { ZetaSegmentedItem } from "./components/segmented-control/segmented-item.js"; +import { ZetaSlider } from "./components/slider/slider.js"; +import { ZetaSliderInputField } from "./components/slider/slider-input-field/slider-input-field.js"; +import { ZetaSnackbar } from "./components/snackbar/snackbar.js"; +import { ZetaStatusChip } from "./components/chips/status-chip/status-chip.js"; +import { ZetaStatusLabel } from "./components/badges/status-label/status-label.js"; +import { ZetaStepper } from "./components/stepper/stepper.js"; +import { ZetaStepperInput } from "./components/stepper-input/stepper-input.js"; +import { ZetaSwitch } from "./components/switch/switch.js"; +import { ZetaSystemBanner } from "./components/system-banner/system-banner.js"; +import { ZetaTabBar } from "./components/tab-bar/tab-bar.js"; +import { ZetaTabItem } from "./components/tab-bar/tab-item/tab-item.js"; +import { ZetaTag } from "./components/badges/tag/tag.js"; +import { ZetaTextInput } from "./components/text-input/text-input.js"; +import { ZetaTooltip } from "./components/tooltip/tooltip.js"; +import { ZetaTopAppbar } from "./components/top-appbar/top-appbar.js"; +import { ZetaUploadItem } from "./components/upload-item/upload-item.js"; +import { ZetaWorkcloudIndicator } from "./components/badges/workcloud-indicator/workcloud-indicator.js"; +export { + ZetaAccordion, + ZetaActionMenuButton, + ZetaAssistChip, + ZetaAvatar, + ZetaAvatarRail, + ZetaBottomSheet, + ZetaBreadcrumb, + ZetaBreadcrumbItem, + ZetaButton, + ZetaButtonGroup, + ZetaButtonGroupItem, + ZetaCard, + ZetaCardBody, + ZetaCardFooter, + ZetaCardHeader, + ZetaCheckbox, + ZetaDialog, + ZetaDropdownMenuButton, + ZetaDropdownMenuItem, + ZetaDroppable, + ZetaFab, + ZetaFileUpload, + ZetaFilterChip, + ZetaGlobalHeader, + ZetaGridMenuItem, + ZetaIcon, + ZetaIconButton, + ZetaIconIndicator, + ZetaIndicator, + ZetaInPageBanner, + ZetaInputChip, + ZetaLabel, + ZetaList, + ZetaListItem, + ZetaNavigationBar, + ZetaNavigationDrawer, + ZetaNavigationDrawerFooter, + ZetaNavigationDrawerHeader, + ZetaNavigationDrawerItem, + ZetaNavigationDrawerSubItem, + ZetaNavigationProfile, + ZetaNavigationRail, + ZetaNavigationRailItem, + ZetaNotificationIndicator, + ZetaPagination, + ZetaPriorityPill, + ZetaProgressBar, + ZetaProgressCircle, + ZetaRadioButton, + ZetaRangeSelector, + ZetaSearch, + ZetaSegmentedControl, + ZetaSegmentedItem, + ZetaSlider, + ZetaSliderInputField, + ZetaSnackbar, + ZetaStatusChip, + ZetaStatusLabel, + ZetaStepper, + ZetaStepperInput, + ZetaSwitch, + ZetaSystemBanner, + ZetaTabBar, + ZetaTabItem, + ZetaTag, + ZetaTextInput, + ZetaTooltip, + ZetaTopAppbar, + ZetaUploadItem, + ZetaWorkcloudIndicator +}; diff --git a/src/mixins/breakpoints.styles.js b/src/mixins/breakpoints.styles.js new file mode 100644 index 0000000..d807e7b --- /dev/null +++ b/src/mixins/breakpoints.styles.js @@ -0,0 +1,12 @@ +import { css } from "lit"; +export default css` + /* Once Firefox 127 is released, we can convert this to CSS @property code. Maybe...? +--mobile-max-width: 1024px; + +@property --sm { + @media (max-width: #{$mobile-max-width}) { + @content; + } +} +*/ +`; diff --git a/src/mixins/contour.ts b/src/mixins/contour.ts new file mode 100644 index 0000000..d155ef0 --- /dev/null +++ b/src/mixins/contour.ts @@ -0,0 +1,49 @@ +import type { LitElement } from "lit"; +import { css } from "lit"; +import { property } from "lit/decorators.js"; +import { type Constructor } from "./utils.js"; + +declare class ContourableInterface { + rounded: boolean; +} + +/** + * Mixin to add Contourable to component. + * + * Adds rounded attribute, and associated styles. + * + * @param superClass - LitElement to add mixin to + * @returns - component with mixin applied. + */ +export const Contourable = <T extends Constructor<LitElement>>(superClass: T) => { + class ContourableClass extends superClass { + /** + * Whether the component is rounded or sharp. + * + * When true, rounded will set the border radius of the first child, and any children with class 'contourable-target' to `--radius-minimal`. + * + * Otherwise, this value will be `--radius-none`. + */ + @property({ type: Boolean, reflect: true }) rounded?: boolean; + + static styles = [ + (superClass as unknown as typeof LitElement).styles ?? [], + css` + :host([rounded]) { + --contour: var(--radius-minimal); + --computed-icon-font: "zeta-icons-round"; + } + :host([rounded="false"]) { + --contour: var(--radius-none); + --computed-icon-font: "zeta-icons-sharp"; + } + :host > *, + :host .contourable-target { + --icon-font: var(--computed-icon-font, "zeta-icons-sharp"); + border-radius: var(--contour, var(--radius-none)); + } + ` + ]; + } + return ContourableClass as Constructor<ContourableInterface & LitElement> & T; +}; diff --git a/src/mixins/flavor.styles.js b/src/mixins/flavor.styles.js new file mode 100644 index 0000000..ffba0d4 --- /dev/null +++ b/src/mixins/flavor.styles.js @@ -0,0 +1,72 @@ +import { css } from "lit"; +export default css` + :host([flavor="primary"]:not([disabled])) > :first-child { + background-color: var(--flavor-background-color, var(--state-primary-enabled)); + color: var(--main-inverse); + + &:hover { + background-color: var(--state-primary-hover); + } + &:active { + background-color: var(--state-primary-selected); + } + } + :host([flavor="secondary"]:not([disabled])) > :first-child { + background-color: var(--flavor-background-color, var(--state-secondary-enabled)); + color: var(--main-inverse); + &:hover { + background-color: var(--state-secondary-hover); + } + &:active { + background-color: var(--state-secondary-selected); + } + } + :host([flavor="positive"]:not([disabled])) > :first-child { + background-color: var(--flavor-background-color, var(--state-positive-enabled)); + color: var(--main-inverse); + &:hover { + background-color: var(--state-positive-hover); + } + &:active { + background-color: var(--state-positive-selected); + } + } + :host([flavor="negative"]:not([disabled])) > :first-child { + background-color: var(--flavor-background-color, var(--state-negative-enabled)); + color: var(--main-inverse); + &:hover { + background-color: var(--state-negative-hover); + } + &:active { + background-color: var(--state-negative-selected); + } + } + :host([flavor="outline"]:not([disabled])) > :first-child, + :host([flavor="outline-subtle"]:not([disabled])) > :first-child, + :host([flavor="text"]:not([disabled])) > :first-child, + :host([flavor="basic"]:not([disabled])) > :first-child, + :host([flavor="basic-negative"]:not([disabled])) > :first-child { + background-color: var(--flavor-background-color, var(--state-default-enabled)); + &:hover { + background-color: var(--surface-hover); + } + &:active { + background-color: var(--surface-selected); + } + } + :host([flavor="outline"]:not([disabled])) > :first-child { + color: var(--main-primary); + box-shadow: 0 0 0 var(--border-size-small) var(--border-primary-main); + } + :host([flavor="outline-subtle"]:not([disabled])) > :first-child { + color: var(--main-default); + box-shadow: 0 0 0 var(--border-size-small) var(--border-default); + } + :host([flavor="text"]:not([disabled])) > :first-child { + color: var(--main-primary); + } + + :host([disabled]) > * { + background: var(--flavor-disabled-background-color, var(--surface-disabled)); + } +`; diff --git a/src/mixins/flavor.ts b/src/mixins/flavor.ts new file mode 100644 index 0000000..63168c7 --- /dev/null +++ b/src/mixins/flavor.ts @@ -0,0 +1,40 @@ +import type { LitElement } from "lit"; +import type { Constructor } from "./utils.js"; +import { property } from "lit/decorators.js"; +import styles from "./flavor.styles.js"; + +export type Flavor = "primary" | "secondary" | "positive" | "negative" | "outline" | "outline-subtle" | "text" | "inverse"; + +export declare class FlavoredInterface { + flavor: Flavor; +} + +/** + * Mixin to add flavor to component. + * + * Adds flavor attribute and associated styles. + * @cssproperty --flavor-background-color an override to set the background color of the element. + * @cssproperty --flavor-disabled-background-color an override to set the background color of the element when it is disabled. + * @param superClass - LitElement to add mixin to + * @returns - component with mixin applied. + */ +export const Flavored = <T extends Constructor<LitElement>>(superClass: T) => { + class FlavoredClass extends superClass { + /** Flavor of component. + * + * Values: + * * primary - blue background. + * * secondary - yellow background. + * * positive - green background. + * * negative - red background. + * * outline - primary outline only. + * * outline-subtle - grey outline only. + * * text - primary text only. + */ + @property({ type: String, reflect: true }) flavor: Flavor = "primary"; + + static styles = [(superClass as unknown as typeof LitElement).styles ?? [], styles]; + } + + return FlavoredClass as Constructor<FlavoredInterface & LitElement> & T; +}; diff --git a/src/mixins/form-field.ts b/src/mixins/form-field.ts new file mode 100644 index 0000000..715f706 --- /dev/null +++ b/src/mixins/form-field.ts @@ -0,0 +1,379 @@ +/* eslint-disable @typescript-eslint/unbound-method */ +import type { LitElement, PropertyValues } from "lit"; +import { html, nothing } from "lit"; +import { property, query, queryAssignedNodes, state } from "lit/decorators.js"; +import { type AbstractConstructor } from "./utils.js"; +import { ifDefined } from "lit/directives/if-defined.js"; +import { live } from "lit/directives/live.js"; +export type InputType = + | "checkbox" + | "text" + | "textarea" + | "password" + | "time" + | "date" + | "radio" + | "search" + | "text-dropdown" + | "checkbox-dropdown" + | "radio-dropdown" + | "slider" + | "range-selector"; //Extend this when adding more form controls + +//TODO add all properties here +declare abstract class FormFieldInterface /* extends InteractiveInterface*/ { + abstract type: InputType; + name: string; + required: boolean; + value: string; + disabled: boolean; + checked?: boolean; + indeterminate: boolean; + input: HTMLInputElement; + internals: ElementInternals; + abstract handleChange(event: Event): void; +} + +/** + * Class mixin to add form control properties to a component. + * + * @slot - Slot for a label. + * @param superClass - LitElement to add mixin to + * @returns - component with mixin applied. + */ +export const FormField = <T extends AbstractConstructor<LitElement>>(superClass: T) => { + /** + * Represents a form control element that can be associated with a form. + */ + abstract class FormFieldClass extends superClass { + static formAssociated = true; + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + constructor(...args: any[]) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + super(...args); + this.internals = this.attachInternals(); + } + @state() internals: ElementInternals; + @query("input") input!: HTMLInputElement; + @query("label") label?: HTMLLabelElement; + @queryAssignedNodes() slottedElements!: Array<HTMLElement>; + + /** + * The default value of the form control. This is the initial "value" when created + */ + protected _defaultValue: string | null = ""; + /** + * The default checked value of the form control if it is a checkbox/switch. + * This is the initial "checked" when created + */ + protected _defaultChecked?: boolean = false; + + /** + * The type of the form control. + */ + abstract type: InputType; + + private get isCheckable() { + return this.type === "checkbox" || this.type === "radio"; + } + + /** Whether inputted text is automatically capitalized and how. Only takes effect on non-keyboard entry */ + @property({ type: String }) autoCapitalize?: "none" | "off" | "sentences" | "on" | "words" | "characters"; + //TODO the enclosing form should override this with its own same attribute. this needs testing. + @property({ type: String }) autoComplete?: AutoFill; + + /** + * The name of the form control. + */ + @property({ type: String, reflect: true }) name: string = ""; + + /** + * The id of the form control. + */ + @property({ type: String, reflect: true }) id: string = ""; + + /** + * Indicates whether the form control is required to be filled out before submitting the form. + */ + @property({ type: Boolean, reflect: true }) required: boolean = false; + + /** + * The value of the Form Control that is submitted when part of a form. + */ + @property({ type: String }) value: string | null = null; + + _checked?: boolean; + /** + * The state of the Switch or Checkbox that is submitted when part of a form. + */ + @property({ type: Boolean, reflect: true }) + get checked() { + return this._checked; + } + set checked(value: boolean | undefined) { + this._checked = value; + this.internals.setFormValue(value ? (this.value !== null ? this.value : "on") : null); + } + /** + * The state of the Switch or Checkbox defined by a mixed or unknown state. + */ + @property({ type: Boolean, reflect: true }) indeterminate: boolean = false; + + /** Placeholder text shown when value is empty. */ + @property({ type: String, reflect: true }) placeholder? = ""; + + /** Placeholder text shown when value is empty. */ + @property({ type: Boolean, reflect: true }) readOnly?: boolean; + + /** Whether to spellcheck the input */ + @property({ type: Boolean }) spellCheck?: boolean | "default"; + /** + * Boolean for if component is disabled. + * + * This will apply disabled styles. + */ + @property({ type: Boolean, reflect: true }) disabled: boolean = false; + + public checkValidity(): boolean { + return this.internals.checkValidity(); + } + + public reportValidity(): boolean { + return this.internals.reportValidity(); + } + + public get validity(): ValidityState { + return this.internals.validity; + } + + public get validationMessage(): string { + return this.internals.validationMessage; + } + + private getValue(value: string | null, checked: boolean | undefined): string | null { + if (this.isCheckable) { + if (checked) { + return this.value ?? "on"; + } else { + return null; + } + } + return value; + } + + connectedCallback() { + super.connectedCallback(); + if (this.checked !== undefined) { + this._defaultChecked = this.checked; + } + this._defaultValue = this.getValue(this.value, this._defaultChecked); + + // Removed the below line as it violates accessability rules https://dequeuniversity.com/rules/axe/4.8/nested-interactive?application=axeAPI + // if (this.internals.role) this.role = this.internals.role; + if (this.isCheckable) { + this.ariaChecked = String(this.checked); + } + } + + firstUpdated(_changedProperties: PropertyValues): void { + super.firstUpdated(_changedProperties); + this.setToInitialValues(); + + if (this.isCheckable) this.addEventListener("click", this.input.click); + } + + disconnectedCallback(): void { + super.disconnectedCallback(); + if (this.isCheckable) this.removeEventListener("click", this.input.click); + } + + formResetCallback() { + this.setToInitialValues(); + } + + setToInitialValues() { + this._setValue({ checked: this._defaultChecked, value: this._defaultValue }); + } + + //Might need to override or abstract: setValidity(flags: ValidityStateFlags, message?: string, anchor?: HTMLElement) + + private handleInput(event: Event) { + const input = event.target as HTMLInputElement; + this._setValue(input); + } + + private _setValue(input: { checked?: boolean; value: string | null }) { + const newVal = this.getValue(input.value, input.checked); + + if (this.isCheckable) { + this.checked = input.checked; + this.indeterminate = false; + } else { + this.value = newVal; + this.internals.setFormValue(newVal); + } + } + + private _handleChange(event: Event) { + this.handleInput(event); + this.handleChange(event); + } + + /** + * Event fired when the input value changes: + * this specifically occurs when th user commits the change explicitly (i,e, date picker, file picker) + * or if it is a text-like input, when the user loses focus from the input. + * or if it is a checkbox-like input, when the checkbox is toggled. + * or if it is a radio-like input, when the radio is selected (but not unselected). + */ + handleChange(_event: Event): void {} + + /* + max=${'date'|'month'|'week'|'time'|'datetime-local'|'number'|'range'} + maxlength=${'text'|'search'|'tel'|'url'|'email'|'password'} + min=${'date'|'month'|'week'|'time'|'datetime-local'|'number'|'range'} + minlength=${'text'|'search'|'tel'|'url'|'email'|'password'} + step=${'date'|'month'|'week'|'time'|'datetime-local'|'number'|'range'} + + multiple=${'email'} + + //autofocus? + aria-describedby=${} + aria-label=${this.label} + placeholder=${this.placeholder} + autocomplete=${this.autoComplete} + spellcheck=${this.spellCheck} + */ + + protected override render() { + const notUrlEmailPassword = /*this.type !== 'url' && this.type !== 'email' && */ this.type !== "password"; + + switch (this.type) { + case "checkbox": + case "radio": + return html`<input + type=${this.type} + id=${ifDefined(this.id !== "" ? this.id : undefined)} + name=${ifDefined(this.name)} + ?required=${this.required} + ?disabled=${this.disabled} + aria-label=${this.ariaLabel || nothing} + aria-disabled=${this.disabled ? "true" : "false"} + aria-required=${this.required ? "true" : "false"} + value=${ifDefined(live(this.value))} + .checked=${live(this.checked !== undefined ? this.checked : false)} + aria-checked=${this.checked ? "true" : "false"} + .indeterminate=${live(this.indeterminate)} + @input=${this.handleInput} + @change=${this._handleChange} + /> `; + case "textarea": + return html`<textarea + id=${ifDefined(this.id !== "" ? this.id : undefined)} + name=${ifDefined(this.name)} + ?disabled=${this.disabled} + aria-disabled=${this.disabled ? "true" : "false"} + ?required=${this.required} + aria-required=${this.required ? "true" : "false"} + autocapitalize=${ifDefined(this.autoCapitalize)} + autocomplete=${ifDefined(this.autoComplete)} + placeholder=${ifDefined(this.placeholder)} + ?readonly=${this.readOnly} + spellcheck=${ifDefined(notUrlEmailPassword ? this.spellCheck : undefined)} + @input=${this.handleInput} + @change=${this._handleChange} + .value=${live(this.value ?? "")} + ></textarea>`; + case "text-dropdown": + case "checkbox-dropdown": + case "radio-dropdown": + return html`<input + type=${this.type} + id=${ifDefined(this.id !== "" ? this.id : undefined)} + name=${ifDefined(this.name)} + ?disabled=${this.disabled} + aria-disabled=${this.disabled ? "true" : "false"} + ?required=${this.required} + aria-required=${this.required ? "true" : "false"} + autocapitalize=${ifDefined(notUrlEmailPassword ? this.autoCapitalize : undefined)} + autocomplete=${ifDefined(this.autoComplete)} + placeholder=${ifDefined(this.placeholder)} + ?readonly=${this.readOnly} + .value=${live(this.value ?? "")} + @input=${this.handleInput} + @change=${this._handleChange} + ?hidden=${true} + /> `; + case "slider": + return html`<input + type="text" + id=${ifDefined(this.id !== "" ? this.id : undefined)} + .id=${this.id} + name=${ifDefined(this.name)} + ?disabled=${this.disabled} + aria-disabled=${this.disabled ? "true" : "false"} + ?required=${this.required} + aria-required=${this.required ? "true" : "false"} + ?readonly=${this.readOnly} + .value=${live(this.value ?? "")} + @input=${this.handleInput} + @change=${this._handleChange} + />`; + // ?hidden=${true} + case "range-selector": + return html`<input + type="text" + id=${ifDefined(this.id !== "" ? this.id : undefined)} + .id=${this.id} + name=${ifDefined(this.name)} + ?disabled=${this.disabled} + aria-disabled=${this.disabled ? "true" : "false"} + ?required=${this.required} + aria-required=${this.required ? "true" : "false"} + ?readonly=${this.readOnly} + .value=${live(this.value ?? "")} + @input=${this.handleInput} + @change=${this._handleChange} + ?hidden=${true} + />`; + default: + return html`<input + type=${this.type} + id=${ifDefined(this.id !== "" ? this.id : undefined)} + name=${ifDefined(this.name)} + ?disabled=${this.disabled} + aria-disabled=${this.disabled ? "true" : "false"} + ?required=${this.required} + aria-required=${this.required ? "true" : "false"} + autocapitalize=${ifDefined(notUrlEmailPassword ? this.autoCapitalize : undefined)} + autocomplete=${ifDefined(this.autoComplete)} + placeholder=${ifDefined(this.placeholder)} + ?readonly=${this.readOnly} + .value=${live(this.value ?? "")} + @input=${this.handleInput} + @change=${this._handleChange} + /> `; + + /* + -spellcheck + list + placeholder=$ {'text'|'search'|'tel'|'url'|'email'|'password'} + pattern=$ {'text'|'search'|'tel'|'url'|'email'|'password'} + readonly + */ + } + /* There is scope to add more form control events in the above. I.e: + * @cancel=${this.handleCancel} + * @drag=${} + * @dragend=${} + * @dragenter=${} + * @dragleave=${} + * @dragover=${} + * @dragstart=${} + * @drop=${} + */ + } + } + return FormFieldClass as AbstractConstructor<FormFieldInterface & LitElement> & T; +}; diff --git a/src/mixins/interactive.styles.js b/src/mixins/interactive.styles.js new file mode 100644 index 0000000..1269098 --- /dev/null +++ b/src/mixins/interactive.styles.js @@ -0,0 +1,34 @@ +import { css } from "lit"; +export default css` + :host { + user-select: none; + } + + :host(:not([disabled])) .interactive-target, + :host(:not([disabled])) > :not(:has(.interactive-target)):first-child { + cursor: pointer; + } + + /* :host(:not([disabled]):hover) .interactive-target { + background: var(--surface-hover); + } */ + + /* :host(:not([disabled]):active) .interactive-target { + background: var(--surface-selected); + } */ + + :host([disabled]) > *, + :host([disabled]) ::slotted(zeta-icon) { + cursor: not-allowed; + --icon-color: var(--main-disabled); + color: var(--main-disabled); + } + + :host(:focus-visible) .interactive-target, + .interactive-target:focus-visible { + outline-width: var(--border-size-medium); + outline-color: var(--border-primary); + outline-style: solid; + z-index: 1; + } +`; diff --git a/src/mixins/interactive.ts b/src/mixins/interactive.ts new file mode 100644 index 0000000..1a8dc11 --- /dev/null +++ b/src/mixins/interactive.ts @@ -0,0 +1,83 @@ +import type { LitElement, PropertyValues } from "lit"; +import { property, query } from "lit/decorators.js"; +import type { Constructor } from "./utils.js"; +import styles from "./interactive.styles.js"; + +export declare class InteractiveInterface { + disabled: boolean; +} + +/** + * Mixin to add interactive states to component. + * + * Adds disabled attribute and associated styles. + * + * @param superClass - LitElement to add mixin to + * @returns - component with mixin applied. + */ +export const Interactive = <T extends Constructor<LitElement>>(superClass: T) => { + class InteractiveClass extends superClass { + /**@internal tracks what the focus listener has been applied to */ + _listenerTarget?: "target" | "firstChild"; + + /** @internal adds the focus eventListener*/ + firstUpdated(_changedProperties: PropertyValues): void { + super.firstUpdated(_changedProperties); + if (this.interactiveElement) { + this.interactiveElement.addEventListener("focus", this._handleFocus); + this._listenerTarget = "target"; + } else if (this.interactiveChild) { + this.interactiveChild?.classList.add("interactive-target"); + this.interactiveChild?.addEventListener("focus", this._handleFocus); + this._listenerTarget = "firstChild"; + } + } + /** @internal removes the focus eventListener*/ + disconnectedCallback(): void { + super.disconnectedCallback(); + switch (this._listenerTarget) { + case "target": + this.interactiveElement?.removeEventListener("focus", this._handleFocus); + break; + case "firstChild": + this.interactiveChild?.removeEventListener("focus", this._handleFocus); + break; + } + } + + /** @internal This stops internal focus events happening onclick */ + _handleFocus = (event: FocusEvent) => { + if (this.disabled) { + event.preventDefault(); + event.stopPropagation(); + // Revert focus back to previous blurring element + if (event.relatedTarget) { + (event.relatedTarget as HTMLElement).focus(); + } else { + this.blur(); + } + } + return true; + }; + + /** @internal */ + @query(".interactive-target") private readonly interactiveElement?: HTMLElement; + + /** @internal */ + @query(":host > :first-child") private readonly interactiveChild?: HTMLElement; + + /** + * Boolean for if component is disabled. + * + * This will apply disabled styles. + */ + @property({ type: Boolean, reflect: true }) disabled: boolean = false; + /** + * The tab index of the component, used to determine the order in which elements receive focus when the user navigates through the document by pressing the Tab key. + */ + @property({ type: Number }) override tabIndex: number = 0; + + static styles = [(superClass as unknown as typeof LitElement).styles ?? [], styles]; + } + return InteractiveClass as Constructor<InteractiveInterface & LitElement> & T; +}; diff --git a/src/mixins/mixins.ts b/src/mixins/mixins.ts new file mode 100644 index 0000000..c4c3145 --- /dev/null +++ b/src/mixins/mixins.ts @@ -0,0 +1,7 @@ +export * from "./utils.js"; +export * from "./flavor.js"; +export * from "./interactive.js"; +export * from "./popup.js"; +export * from "./size.js"; +export * from "./contour.js"; +export * from "./navigate.js"; diff --git a/src/mixins/navigate.ts b/src/mixins/navigate.ts new file mode 100644 index 0000000..58eb87d --- /dev/null +++ b/src/mixins/navigate.ts @@ -0,0 +1,34 @@ +import { css, type LitElement } from "lit"; +import type { Constructor } from "./utils"; +import { property } from "lit/decorators.js"; + +declare class NavigateInterface { + href: string; +} + +/** + * Mixin to add a href attribute to a given component. + * + * Removes default styling from anchor tags. + * + * @param superClass - LitElement to add mixin to + * @returns - component with mixin applied. + */ +export const Navigate = <T extends Constructor<LitElement>>(superClass: T) => { + class InteractiveClass extends superClass { + /** + * The URL of the component. + */ + @property({ type: String, reflect: true }) href?: string; + + static styles = [ + (superClass as unknown as typeof LitElement).styles ?? [], + css` + a { + text-decoration: none; + } + ` + ]; + } + return InteractiveClass as Constructor<NavigateInterface & LitElement> & T; +}; diff --git a/src/mixins/popup.ts b/src/mixins/popup.ts new file mode 100644 index 0000000..72d9606 --- /dev/null +++ b/src/mixins/popup.ts @@ -0,0 +1,113 @@ +/* eslint-disable @typescript-eslint/unbound-method -- Cannot tie this to a specific line. I wasted hours on this stupid rule */ +import type { LitElement, PropertyValues } from "lit"; +import { css } from "lit"; +import { property, query } from "lit/decorators.js"; +import { type Constructor } from "./utils.js"; +import { ZetaPopupEvent } from "../events.js"; + +declare class PopupInterface { + returnValue: string; + open: boolean; + hide: (returnValue?: string) => Promise<void>; + show: () => Promise<void>; + showModal: () => Promise<void>; + onBarrierClicked: (e: Event) => void; +} + +/** + * Mixin to add make component pop up as a dialog. + * + * @param superClass - LitElement to add mixin to + * @event {CustomEvent<ZetaPopupEvent>} ZetaPopupEvent:open - Fired when the popup is opened. + * @event {CustomEvent<ZetaPopupEvent>} ZetaPopupEvent:close - Fired when the popup is closed. + * @event {CustomEvent<ZetaPopupEvent>} ZetaPopupEvent:cancel - Fired when the popup is cancelled. + * @returns - component with mixin applied. + */ +export const Popup = <T extends Constructor<LitElement>>(superClass: T) => { + class PopupClass extends superClass { + @query("dialog") private readonly dialog!: HTMLDialogElement; + + /** Return value of the dialog. */ + @property({ type: String }) returnValue: string = ""; + + /** Whether component is open or closed. */ + @property({ type: Boolean, reflect: true, attribute: "open" }) + get open() { + return this.dialog?.open ?? false; + } + + async showModal() { + return this._show(true); + } + + async show() { + return this._show(false); + } + + /** @internal */ + async _show(isModal: boolean) { + await this.updateComplete; + if (isModal) { + this.dialog.showModal(); + } else { + this.dialog.show(); + } + + this.dispatchEvent(new ZetaPopupEvent("open").toEvent()); + } + + async hide(returnValue = this.returnValue) { + await this.updateComplete; + this.returnValue = returnValue; + this.dialog.close(returnValue); + } + + async cancel() { + await this.hide("cancel"); + } + + _onClose(_e: Event) { + this.dispatchEvent(new ZetaPopupEvent("close").toEvent()); + } + + _onCancel() { + this.dispatchEvent(new ZetaPopupEvent("cancel").toEvent()); + } + + onBarrierClicked(e: Event) { + if (e.target !== this.dialog) { + return; + } + e.preventDefault(); + void this.cancel(); + } + + /** @internal adds all the eventListeners*/ + firstUpdated(_changedProperties: PropertyValues): void { + super.firstUpdated(_changedProperties); + + if (this.dialog) { + this.dialog.addEventListener("click", this.onBarrierClicked.bind(this)); + this.dialog.addEventListener("close", this._onClose.bind(this)); + this.dialog.addEventListener("cancel", this._onCancel.bind(this)); + } + } + /** @internal removes all the eventListeners*/ + disconnectedCallback(): void { + super.disconnectedCallback(); + this.dialog.removeEventListener("click", this.onBarrierClicked); + this.dialog.removeEventListener("close", this._onClose); + this.dialog.removeEventListener("cancel", this._onCancel); + } + + static styles = [ + (superClass as unknown as typeof LitElement).styles ?? [], + css` + dialog::backdrop { + background-color: var(--surface-overlay); + } + ` + ]; + } + return PopupClass as Constructor<PopupInterface & LitElement> & T; +}; diff --git a/src/mixins/size.ts b/src/mixins/size.ts new file mode 100644 index 0000000..e3e2e14 --- /dev/null +++ b/src/mixins/size.ts @@ -0,0 +1,26 @@ +import type { LitElement } from "lit"; +import { property } from "lit/decorators.js"; +import { type Constructor } from "./utils.js"; +import styles from "./interactive.styles.js"; + +declare class SizeInterface { + size: "small" | "medium" | "large"; +} + +/** + * Mixin to add sizes component. + * + * Only adds size property, does not apply styles. + * + * @param superClass - LitElement to add mixin to + * @returns - component with mixin applied. + */ +export const Size = <T extends Constructor<LitElement>>(superClass: T) => { + class InteractiveClass extends superClass { + /** Size of component */ + @property({ type: String, reflect: true }) size: "small" | "medium" | "large" = "medium"; + + static styles = [(superClass as unknown as typeof LitElement).styles ?? [], styles]; + } + return InteractiveClass as Constructor<SizeInterface & LitElement> & T; +}; diff --git a/src/mixins/tertiary-interactive.styles.js b/src/mixins/tertiary-interactive.styles.js new file mode 100644 index 0000000..60b3725 --- /dev/null +++ b/src/mixins/tertiary-interactive.styles.js @@ -0,0 +1,15 @@ +import { css } from "lit"; +export default css` + .tertiary-interactive { + background-color: var(--surface-default-inverse); + color: var(--main-inverse); + + &:hover { + background-color: var(--state-inverse-hover) !important; + } + + &:active { + background-color: var(--state-inverse-selected) !important; + } + } +`; diff --git a/src/mixins/utils.ts b/src/mixins/utils.ts new file mode 100644 index 0000000..5358596 --- /dev/null +++ b/src/mixins/utils.ts @@ -0,0 +1,7 @@ +import type { LitElement } from "lit"; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export type Constructor<T extends LitElement> = new (...args: any[]) => T; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export type AbstractConstructor<T extends LitElement> = abstract new (...args: any[]) => T; diff --git a/src/stories/Action Menu/Docs.mdx b/src/stories/Action Menu/Docs.mdx new file mode 100644 index 0000000..06e80f4 --- /dev/null +++ b/src/stories/Action Menu/Docs.mdx @@ -0,0 +1,55 @@ +import { Canvas, Meta, Description, ArgTypes, Title, Stories, Story, Source } from "@storybook/blocks"; + +import * as ActionMenuButton from "./action-menu-button.stories"; + +<Meta of={ActionMenuButton} title="ActionMenu" /> + +<Title /> + +# Action Menu Button + +<Description of="zeta-action-menu-button" /> +<div + style={{ + margin: "25px 30px 0", + padding: "40px 30px", + borderRadius: "4px", + background: "#FFFFFF", + boxShadow: "rgba(0, 0, 0, 0.10) 0 1px 3px 0", + border: "1px solid hsla(203, 50%, 30%, 0.15)" + }} +> + <Story of={ActionMenuButton.ActionMenuButton} /> +</div> + +Use the function field in the items array to define the action to be taken when the item is clicked. + +Pass an array of objects to the `items` property. Each object must have a `label` and an `icon` property, and optionally a `function` property which runs when that item is clicked. + +As you use the action menu button, think about whether you'd rather put the action menu items in a prop or in a slot. Please let the development team know for future reference. + +<div + style={{ + margin: "0 15px", + }}> + + ``` + const items = [ + { label: "Item 1", icon: "star", function: () => console.log("Item 1 clicked")}, + { label: "Item 2", icon: "star", function: () => console.log("Item 2 clicked")}, + { label: "Item 3", icon: "star", function: () => console.log("Item 3 clicked")}, + ]; + + <zeta-action-menu-button + items=${items} + alignment="start" + icon="more_vertical" + rounded=true + size="medium" + flavor="primary"> + </zeta-action-menu-button> + ``` + +</div> + +<ArgTypes of="zeta-action-menu-button" /> diff --git a/src/stories/Action Menu/action-menu-button.stories.ts b/src/stories/Action Menu/action-menu-button.stories.ts new file mode 100644 index 0000000..d504dbf --- /dev/null +++ b/src/stories/Action Menu/action-menu-button.stories.ts @@ -0,0 +1,125 @@ +import type { Meta, StoryObj } from "@storybook/web-components"; +import { html } from "lit"; +import { ZetaActionMenuButton } from "../../components/action-menu/action-menu-button.js"; +import { ZetaIconNameList } from "@zebra-fed/zeta-icons"; + +const meta: Meta<ZetaActionMenuButton> = { + component: "zeta-action-menu-button", + title: "Action Menu", + args: { + rounded: true, + open: false, + icon: "more_vertical", + alignment: "start", + direction: undefined, + flavor: "primary", + items: [ + { + label: "Item 1", + icon: "star", + onClick: () => { + console.log("Item 1 clicked"); + } + }, + { + label: "Item 2", + icon: "star", + onClick: () => { + console.log("Item 2 clicked"); + } + }, + { + label: "Item 3", + icon: "star", + onClick: () => { + console.log("Item 3 clicked"); + } + } + ] + }, + argTypes: { + icon: { + options: ZetaIconNameList, + control: { type: "select" } + }, + alignment: { + options: ["start", "end", "center"], + control: { type: "inline-radio" } + }, + flavor: { + options: ["primary", "secondary", "positive", "negative", "outline", "outline-subtle", "text"], + control: { type: "select" } + }, + direction: { + options: ["left", "right", "bottom", "top", undefined], + control: { + type: "select" + } + }, + items: { + table: { disable: true } + }, + size: { + table: { disable: true } + } + }, + parameters: { + design: { + url: "https://www.figma.com/file/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?type=design&node-id=22391-10146&mode=design&t=DUHsS5bjWB5UW1iG-4" + }, + status: { + type: "designPending" + } + } +}; + +export default meta; + +export const ActionMenuButton: StoryObj<ZetaActionMenuButton> = { + render: args => html` + <div style="display: flex; justify-content: center;"> + <div style="padding: 30px"> + <h4>Small</h4> + <zeta-action-menu-button + .size=${"small"} + ?rounded=${args.rounded} + ?open=${args.open} + .items=${args.items} + .icon=${args.icon} + .alignment=${args.alignment} + .direction=${args.direction} + .flavor=${args.flavor} + > + </zeta-action-menu-button> + </div> + <div style="padding: 30px"> + <h4>Medium</h4> + <zeta-action-menu-button + .size=${"medium"} + ?rounded=${args.rounded} + ?open=${args.open} + .items=${args.items} + .icon=${args.icon} + .alignment=${args.alignment} + .direction=${args.direction} + .flavor=${args.flavor} + > + </zeta-action-menu-button> + </div> + <div style="padding: 30px"> + <h4>Large</h4> + <zeta-action-menu-button + .size=${"large"} + ?rounded=${args.rounded} + ?open=${args.open} + .items=${args.items} + .icon=${args.icon} + .alignment=${args.alignment} + .direction=${args.direction} + .flavor=${args.flavor} + > + </zeta-action-menu-button> + </div> + </div> + ` +}; diff --git a/src/stories/Assets/Docs.mdx b/src/stories/Assets/Docs.mdx new file mode 100644 index 0000000..3b1ff9e --- /dev/null +++ b/src/stories/Assets/Docs.mdx @@ -0,0 +1,14 @@ +import { Canvas, Meta, Description, ArgTypes, Title } from "@storybook/blocks"; +import * as Icon from "./icon.stories"; + +<Meta of={Icon} title="Assets" /> + +<Title /> + +zeta-web provides a variety of assets to use in projects. + +# Icons + +<Description of="zeta-icon" /> +<Canvas of={Icon.Icon} /> +<ArgTypes of="zeta-icon" /> diff --git a/src/stories/Assets/icon.stories.ts b/src/stories/Assets/icon.stories.ts new file mode 100644 index 0000000..c6ba644 --- /dev/null +++ b/src/stories/Assets/icon.stories.ts @@ -0,0 +1,43 @@ +import type { Meta, StoryObj } from "@storybook/web-components"; +import { ZetaIconNameList } from "@zebra-fed/zeta-icons"; +import { ZetaIcon } from "../../components/icon/icon.js"; +import { spreadGenerator } from "../utils.js"; +import { html } from "lit"; +const spread = spreadGenerator(ZetaIcon); + +const meta: Meta<ZetaIcon> = { + component: "zeta-icon", + title: "Assets", + args: { + slot: "alarm", + rounded: true + }, + argTypes: { + slot: { + options: ZetaIconNameList, + control: { type: "select" } + }, + size: { table: { disable: true } }, + color: { table: { disable: true } } + }, + parameters: { + design: { + url: "https://www.figma.com/file/VQ7Aa3rDYB7mgpToI3bZ4D/%F0%9F%A6%93-ZDS---Assets?type=design&node-id=240-6&mode=design" + }, + status: { + type: "ready" + } + } +}; +export default meta; + +export const Icon: StoryObj = { + render: ({ slot, ...args }) => + html` <style> + :root { + ${args["--icon-color"] && `--icon-color: ${args["--icon-color"]}`} ; + ${args["--icon-size"] && `--icon-size: ${args["--icon-size"]}`} ; + } + </style> + <zeta-icon ${spread(args)}>${slot}</zeta-icon>` +}; diff --git a/src/stories/Avatar/Docs.mdx b/src/stories/Avatar/Docs.mdx new file mode 100644 index 0000000..2d34adf --- /dev/null +++ b/src/stories/Avatar/Docs.mdx @@ -0,0 +1,40 @@ +import { Canvas, Meta, Description, ArgTypes, Title } from "@storybook/blocks"; +import * as Avatar from "./avatar.stories"; + +<Meta of={Avatar} title="Avatar" /> + +<Title /> +<Canvas of={Avatar.AvatarWithInitials} /> +<Description of="zeta-avatar" /> +<ArgTypes of={Avatar.AvatarWithInitials} /> + +# Avatar with Initials + +To create an avatar with initials, slot the initials into the main slot of the `zeta-avatar` component. +To change the color of the initials, set the `--avatar-initials-color` CSS variable to the desired color. + +<Canvas of={Avatar.AvatarWithInitials} /> + +# Avatar with Image + +To create an avatar with an image, slot an img element into the main slot of the `zeta-avatar` component. + +<Canvas of={Avatar.AvatarWithImage} /> + +# Avatar with Icon + +To create an avatar with an icon, slot a `zeta-icon` into the main slot of the `zeta-avatar` component. + +<Canvas of={Avatar.AvatarWithIcon} /> + +# Avatar with Status badge + +To add a status badge to an avatar, slot the component into the `status` slot of the `zeta-avatar` component. + +<Canvas of={Avatar.AvatarWithStatus} /> + +# Avatar with Close icon + +To show a close icon on an avatar, set `show-close` to `true`. The component will fire a `@avatar-close` event when this button is clicked. + +<Canvas of={Avatar.AvatarWithCloseIcon} /> diff --git a/src/stories/Avatar/avatar.stories.ts b/src/stories/Avatar/avatar.stories.ts new file mode 100644 index 0000000..4382c7c --- /dev/null +++ b/src/stories/Avatar/avatar.stories.ts @@ -0,0 +1,84 @@ +import type { Meta, StoryObj } from "@storybook/web-components"; +import { ZetaAvatar } from "../../components/avatar/avatar.js"; + +type AvatarStory = ZetaAvatar & { "show-ring": boolean; "show-close": boolean; status: string }; + +const meta: Meta<AvatarStory> = { + title: "Avatar", + component: "zeta-avatar", + args: { + size: "m", + "show-ring": false, + "show-close": false + }, + argTypes: { + size: { + options: ["xxxs", "xxs", "xs", "s", "m", "l", "xl", "xxl", "xxxl"], + control: { + type: "select" + } + }, + showRing: { table: { disable: true } }, + showClose: { table: { disable: true } } + }, + parameters: { + design: { + url: "https://www.figma.com/file/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?type=design&node-id=229-2" + }, + status: { + type: "needsAttention" + } + } +}; + +export default meta; + +export const AvatarWithInitials: StoryObj<AvatarStory> = { + args: { + slot: "WW", + showRing: false, + showClose: false + } +}; + +export const AvatarWithImage: StoryObj<AvatarStory> = { + args: { + slot: "<img src='https://design.zebra.com/img/zeta_web.svg'></img>" + }, + argTypes: { + slot: { table: { disable: true } }, + status: { table: { disable: true } } + } +}; + +export const AvatarWithIcon: StoryObj<AvatarStory> = { + args: { + slot: "<zeta-icon>groups</zeta-icon>" + }, + argTypes: { + slot: { table: { disable: true } }, + status: { table: { disable: true } } + } +}; + +export const AvatarWithStatus: StoryObj<AvatarStory> = { + args: { + slot: "WW", + status: "<zeta-icon-indicator icon='star' slot='status'></zeta-icon-indicator>" + }, + argTypes: { + slot: { table: { disable: true } }, + status: { table: { disable: true } } + } +}; + +export const AvatarWithCloseIcon: StoryObj<AvatarStory> = { + args: { + slot: "WW", + "show-close": true + }, + argTypes: { + slot: { table: { disable: true } }, + status: { table: { disable: true } } + } +}; diff --git a/src/stories/Badges/Docs.mdx b/src/stories/Badges/Docs.mdx new file mode 100644 index 0000000..e706f83 --- /dev/null +++ b/src/stories/Badges/Docs.mdx @@ -0,0 +1,40 @@ +import { Canvas, Meta, Description, ArgTypes, Title } from "@storybook/blocks"; +import * as StatusLabel from "./status-label.stories"; +import * as PriorityPill from "./priority-pill.stories"; +import * as TextBadge from "./text-badge.stories"; +import * as Indicator from "./indicators.stories"; +import * as Tag from "./tag.stories"; + +<Meta of={PriorityPill} title="Badges" /> + +<Title /> + +# Status Label + +<Description of="zeta-status-label" /> +<Canvas of={StatusLabel.StatusLabel} /> +<ArgTypes of="zeta-status-label" /> + +# Priority Pill + +<Description of="zeta-priority-pill" /> +<Canvas of={PriorityPill.PriorityPill} /> +<ArgTypes of="zeta-priority-pill" /> + +# Label / Text Badge + +<Description of="zeta-label" /> +<Canvas of={TextBadge.TextBadge} /> +<ArgTypes of="zeta-label" /> + +# Indicators / Badges + +<Description of="zeta-indicator" /> +<Canvas of={Indicator.Indicator} /> +<ArgTypes of="zeta-indicator" /> + +# Tags + +<Description of="zeta-tag" /> +<Canvas of={Tag.Tag} /> +<ArgTypes of="zeta-tag" /> diff --git a/src/stories/Badges/indicators.stories.ts b/src/stories/Badges/indicators.stories.ts new file mode 100644 index 0000000..46aff35 --- /dev/null +++ b/src/stories/Badges/indicators.stories.ts @@ -0,0 +1,92 @@ +import type { Meta, StoryObj } from "@storybook/web-components"; +import { ZetaIndicator } from "../../components/badges/indicators/indicators.js"; +import "../../components/badges/indicators/indicators.js"; +import { ZetaIconNameList } from "@zebra-fed/zeta-icons"; +import { html } from "lit"; +import { ifDefined } from "lit/directives/if-defined.js"; + +class _ZetaIndicator extends ZetaIndicator { + value!: string; +} + +const meta: Meta<ZetaIndicator> = { + component: "zeta-indicator", + title: "Badges", + args: { + size: "medium", + inverse: false, + rounded: true + }, + argTypes: { + size: { options: ["small", "medium", "large"], control: { type: "inline-radio" } } + }, + parameters: { + design: { + url: "https://www.figma.com/file/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?type=design&node-id=21931-2105" + }, + status: { + type: "needsAttention" + } + } +}; + +export default meta; + +export const Indicator: StoryObj<_ZetaIndicator> = { + argTypes: { + icon: { + options: ZetaIconNameList, + control: { + type: "select" + } + }, + type: { + options: ["icon", "notification"], + control: { + type: "select" + } + } + }, + args: { + value: "1", + type: "notification", + icon: "alarm" + }, + render: args => + html`<zeta-indicator size=${args.size} .inverse=${args.inverse} .rounded=${args.rounded} icon=${args.icon} type=${args.type} value=${args.value}> + </zeta-indicator>` +}; + +export const IconIndicator: StoryObj<_ZetaIndicator> = { + argTypes: { + icon: { + options: ZetaIconNameList, + control: { + type: "select" + } + }, + type: { table: { disable: true } } + }, + args: { + icon: "alarm" + }, + render: args => html` <zeta-icon-indicator size=${args.size} .inverse=${args.inverse} .rounded=${args.rounded} icon=${args.icon}> </zeta-icon-indicator>` +}; + +export const NotificationIndicator: StoryObj<_ZetaIndicator> = { + args: { + value: "1" + }, + argTypes: { + icon: { table: { disable: true } }, + type: { table: { disable: true } }, + value: { + control: { + type: "text" + } + } + }, + render: args => + html` <zeta-notification-indicator size=${args.size} .inverse=${args.inverse} .rounded=${args.rounded} value=${ifDefined(args.value)}> + </zeta-notification-indicator>` +}; diff --git a/src/stories/Badges/priority-pill.stories.ts b/src/stories/Badges/priority-pill.stories.ts new file mode 100644 index 0000000..06c6cb2 --- /dev/null +++ b/src/stories/Badges/priority-pill.stories.ts @@ -0,0 +1,23 @@ +import type { Meta, StoryObj } from "@storybook/web-components"; +import { ZetaPriorityPill } from "../../components/badges/priority-pill/priority-pill.js"; + +const meta: Meta<ZetaPriorityPill> = { + title: "Badges", + component: "zeta-priority-pill", + args: { + text: "Priority", + number: 1, + rounded: true + }, + parameters: { + design: { + url: "https://www.figma.com/file/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?type=design&node-id=21931-2105&mode=design&t=j9Cv98TDx5BKLbgS-4" + }, + status: { + type: "needsAttention" + } + } +}; +export default meta; + +export const PriorityPill: StoryObj<ZetaPriorityPill> = {}; diff --git a/src/stories/Badges/status-label.stories.ts b/src/stories/Badges/status-label.stories.ts new file mode 100644 index 0000000..1996d85 --- /dev/null +++ b/src/stories/Badges/status-label.stories.ts @@ -0,0 +1,39 @@ +import type { Meta, StoryObj } from "@storybook/web-components"; +import { ZetaIconNameList } from "@zebra-fed/zeta-icons"; +import { ZetaStatusLabel } from "../../components/badges/status-label/status-label.js"; + +const meta: Meta<ZetaStatusLabel> = { + title: "Badges", + component: "zeta-status-label", + args: { + rounded: true, + status: "neutral", + icon: undefined, + text: "Label" + }, + argTypes: { + status: { + options: ["neutral", "info", "positive", "warning", "negative"], + control: { + type: "select" + } + }, + icon: { + options: ZetaIconNameList, + control: { + type: "select" + } + } + }, + parameters: { + design: { + url: "https://www.figma.com/file/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?type=design&node-id=21836-34789&mode=design&t=j9Cv98TDx5BKLbgS-4" + }, + status: { + type: "ready" + } + } +}; +export default meta; + +export const StatusLabel: StoryObj<ZetaStatusLabel> = {}; diff --git a/src/stories/Badges/tag.stories.ts b/src/stories/Badges/tag.stories.ts new file mode 100644 index 0000000..ddf3fe6 --- /dev/null +++ b/src/stories/Badges/tag.stories.ts @@ -0,0 +1,29 @@ +import type { Meta, StoryObj } from "@storybook/web-components"; +import { ZetaTag } from "../../components/badges/tag/tag.js"; + +const meta: Meta<ZetaTag> = { + title: "Badges", + component: "zeta-tag", + args: { + text: "Tag", + rounded: false + }, + argTypes: { + point: { + options: ["left", "right"], + control: { type: "inline-radio" } + } + }, + parameters: { + design: { + url: "https://www.figma.com/file/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?type=design&node-id=22000-10073" + }, + status: { + type: "ready" + } + } +}; + +export default meta; + +export const Tag: StoryObj<ZetaTag> = {}; diff --git a/src/stories/Badges/text-badge.stories.ts b/src/stories/Badges/text-badge.stories.ts new file mode 100644 index 0000000..baab08f --- /dev/null +++ b/src/stories/Badges/text-badge.stories.ts @@ -0,0 +1,31 @@ +import type { Meta, StoryObj } from "@storybook/web-components"; +import { ZetaLabel } from "../../components/badges/label/label.js"; + +const meta: Meta<ZetaLabel> = { + title: "Badges", + component: "zeta-label", + args: { + text: "label", + rounded: true + }, + argTypes: { + status: { + options: ["neutral", "info", "positive", "warning", "negative"], + control: { + type: "select" + } + } + }, + parameters: { + design: { + url: "https://www.figma.com/file/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?type=design&node-id=21926-1007" + }, + status: { + type: "ready" + } + } +}; + +export default meta; + +export const TextBadge: StoryObj<ZetaLabel> = {}; diff --git a/src/stories/Bottom Sheet/Docs.mdx b/src/stories/Bottom Sheet/Docs.mdx new file mode 100644 index 0000000..e79e0e4 --- /dev/null +++ b/src/stories/Bottom Sheet/Docs.mdx @@ -0,0 +1,26 @@ +import { Canvas, Meta, Description, ArgTypes, Title } from "@storybook/blocks"; +import * as BottomSheet from "./bottom-sheet.stories"; + +<Meta of={BottomSheet} title="Bottom Sheet" /> + +<Title /> +<Description of="zeta-bottom-sheet" /> +<ArgTypes of={BottomSheet.BottomSheetList} /> + +## Bottom Sheet List + +Placing `zeta-list-item`'s inside `zeta-bottom-sheet` will create a list of items in the bottom sheet. + +<Canvas of={BottomSheet.BottomSheetList} /> + +## Bottom Sheet Grid + +Placing `zeta-grid-menu-item`'s inside `zeta-bottom-sheet` will create a grid of items in the bottom sheet. + +<Canvas of={BottomSheet.BottomSheetGrid} /> + +## Bottom Sheet Generic Content + +Placing any content inside `zeta-bottom-sheet` will be directly rendered to the bottom sheet. + +<Canvas of={BottomSheet.BottomSheetGenericContent} /> diff --git a/src/stories/Bottom Sheet/bottom-sheet.stories.ts b/src/stories/Bottom Sheet/bottom-sheet.stories.ts new file mode 100644 index 0000000..da96f8d --- /dev/null +++ b/src/stories/Bottom Sheet/bottom-sheet.stories.ts @@ -0,0 +1,172 @@ +import type { Meta, StoryObj } from "@storybook/web-components"; +import { ZetaBottomSheet } from "../../components/bottom-sheets/bottom-sheet"; +import { html } from "lit"; +import "../../components/list/list-item/list-item"; +import "../../components/icon/icon"; +import "../../components/grid-menu-item/grid-menu-item"; + +const meta: Meta<ZetaBottomSheet> = { + component: "zeta-bottom-sheet", + title: "Bottom Sheet", + args: { + headerText: "Title", + headerAlignment: "start", + isExpanded: true + }, + argTypes: { + headerText: { + control: { + type: "text" + } + }, + headerAlignment: { + options: ["start", "center"], + control: { type: "inline-radio" } + }, + isGrid: { table: { disable: true } }, + isGenericContent: { table: { disable: true } } + }, + parameters: { + design: { + url: "https://www.figma.com/design/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?m=auto&node-id=229-9" + }, + status: { + type: "designPending" + } + } +}; + +export default meta; + +export const BottomSheetList: StoryObj<ZetaBottomSheet> = { + render: args => { + return html` + <zeta-bottom-sheet headerText="${args.headerText}" headerAlignment="${args.headerAlignment}" ?isExpanded=${args.isExpanded} ?isGrid="${args.isGrid}"> + <zeta-list-item @click=${() => console.log("Item 1 clicked")} headline=${"Item 1"}> + <zeta-icon slot="leading">star</zeta-icon> + <zeta-icon slot="trailing">chevron_left</zeta-icon> + </zeta-list-item> + <zeta-list-item @click=${() => console.log("Item 2 clicked")} headline=${"Item 2"}> + <zeta-icon slot="leading">star</zeta-icon> + <zeta-icon slot="trailing">chevron_left</zeta-icon> + </zeta-list-item> + <zeta-list-item @click=${() => console.log("Item 3 clicked")} headline=${"Item 3"}> + <zeta-icon slot="leading">star</zeta-icon> + <zeta-icon slot="trailing">chevron_left</zeta-icon> + </zeta-list-item> + <zeta-list-item @click=${() => console.log("Item 4 clicked")} headline=${"Item 4"}> + <zeta-icon slot="leading">star</zeta-icon> + <zeta-icon slot="trailing">chevron_left</zeta-icon> + </zeta-list-item> + <zeta-list-item @click=${() => console.log("Item 5 clicked")} headline=${"Item 5"}> + <zeta-icon slot="leading">star</zeta-icon> + <zeta-icon slot="trailing">chevron_left</zeta-icon> + </zeta-list-item> + <zeta-list-item @click=${() => console.log("Item 6 clicked")} headline=${"Item 6"}> + <zeta-icon slot="leading">star</zeta-icon> + <zeta-icon slot="trailing">chevron_left</zeta-icon> + </zeta-list-item> + </zeta-bottom-sheet> + `; + } +}; + +export const BottomSheetGrid: StoryObj<ZetaBottomSheet> = { + render: args => { + return html` + <zeta-bottom-sheet headerText=${args.headerText} headerAlignment=${args.headerAlignment} ?isExpanded=${args.isExpanded} ?isGrid=${args.isGrid}> + <zeta-grid-menu-item + @click=${() => console.log("Item 1 clicked")} + .rounded=${true} + .active=${true} + icon=${"star"} + label=${"Label"} + .notificationValue=${true} + > + </zeta-grid-menu-item> + <zeta-grid-menu-item + @click=${() => console.log("Item 2 clicked")} + .rounded=${true} + .active=${false} + icon=${"star"} + label=${"Label"} + .notificationValue=${false} + > + </zeta-grid-menu-item> + <zeta-grid-menu-item + @click=${() => console.log("Item 3 clicked")} + .rounded=${true} + .active=${false} + icon=${"star"} + label=${"Label"} + .notificationValue=${false} + > + </zeta-grid-menu-item> + <zeta-grid-menu-item + @click=${() => console.log("Item 4 clicked")} + .rounded=${true} + .active=${false} + icon=${"star"} + label=${"Label"} + .notificationValue=${false} + > + </zeta-grid-menu-item> + <zeta-grid-menu-item + @click=${() => console.log("Item 5 clicked")} + .rounded=${true} + .active=${false} + icon=${"star"} + label=${"Label"} + .notificationValue=${false} + > + </zeta-grid-menu-item> + <zeta-grid-menu-item + @click=${() => console.log("Item 6 clicked")} + .rounded=${true} + .active=${false} + icon=${"star"} + label=${"Label"} + .notificationValue=${false} + > + </zeta-grid-menu-item> + </zeta-bottom-sheet> + `; + } +}; + +export const BottomSheetGenericContent: StoryObj<ZetaBottomSheet> = { + render: args => { + return html` + <zeta-bottom-sheet headerText="${args.headerText}" headerAlignment="${args.headerAlignment}" ?isExpanded=${args.isExpanded} ?isGrid="${args.isGrid}"> + <div style="padding: 16px;"> + <h1 style="margin: 0;">Generic Content</h1> + <p style="margin: 0;">This is generic content</p> + <div style="margin-top: 16px;"> + <h2>Section 1</h2> + <p>This is the first section of the generic content.</p> + </div> + <div style="margin-top: 16px;"> + <h2>Section 2</h2> + <p>This is the second section of the generic content.</p> + </div> + <div style="margin-top: 16px;"> + <p>This section has an image</p> + <img src="https://placehold.co/250x180/png" /> + </div> + <div style="margin-top: 16px;"> + <h2>Section 3</h2> + <p>This is the third section of the generic content.</p> + </div> + <div style="margin-top: 16px;"> + <h2>Section 4</h2> + <p>This is the fourth section of the generic content.</p> + </div> + <div style="margin-top: 16px;"> + <h2>Section 5</h2> + <p>This is the fifth section of the generic content.</p> + </div> + </div> + </zeta-bottom-sheet> + `; + } +}; diff --git a/src/stories/Breadcrumbs/Docs.mdx b/src/stories/Breadcrumbs/Docs.mdx new file mode 100644 index 0000000..0365d23 --- /dev/null +++ b/src/stories/Breadcrumbs/Docs.mdx @@ -0,0 +1,69 @@ +import { Canvas, Meta, Description, ArgTypes, Title, Source, Story } from "@storybook/blocks"; +import * as Breadcrumb from "./breadcrumb.stories"; +import * as BreadcrumbItem from "./breadcrumb-item.stories"; +import "../../components/icon/icon"; + +<Meta of={Breadcrumb} title="Breadcrumb" /> + +<Title /> + +<Description of="zeta-breadcrumb" /> + +<div style={{padding: '15px', display: "flex"}}> + +> "You leave a trail of breadcrumbs any wolf could follow, then act shocked when a wolf is outside your door." - Victor LaValle + +</div> + +## Breadcrumb Items + +To display breadcrumb items you must place the `<zeta-breadcrumb-item>` component inside the `<zeta-breadcrumb>` component. + +The chevron separator is automatically applied before each breadcrumb item except the first one. So you don't need to add it manually. + +Use the `<zeta-icon>` component with the slot name "icon" to place an icon in a breadcrumb item. + +Like this: + +<Canvas of={BreadcrumbItem.BreadcrumbItem} /> + +```html +<zeta-breadcrumb-item> + <zeta-icon slot="icon">star</zeta-icon> + Icon before with separator +</zeta-breadcrumb-item> +``` + +<ArgTypes of={BreadcrumbItem} /> + +--- + +--- + +# Breadcrumb + +## 2 Items + +<Canvas of={Breadcrumb.Breadcrumb2Items} /> + +## 3 Items + +<Canvas of={Breadcrumb.Breadcrumb3Items} /> + +## 4 Items + +<Canvas of={Breadcrumb.Breadcrumb4Items} /> + +## Truncated + +<Canvas of={Breadcrumb.BreadcrumbTruncated} /> + +The `maxItems` prop allows you to specify the maximum number of items to display in the breadcrumb. If the number of items exceeds the maximum, the breadcrumb will truncate the items in the middle and display a "More Menu". + +Specify maxItems like this: `<zeta-breadcrumb maxItems={4}>...</zeta-breadcrumb>` + +You must not programmatically change the `maxItems` prop. It is a static prop that should be set when the component is first rendered. + +Clicking the ellipse button will reveal the hidden items. + +<ArgTypes of={Breadcrumb} /> diff --git a/src/stories/Breadcrumbs/breadcrumb-item.stories.ts b/src/stories/Breadcrumbs/breadcrumb-item.stories.ts new file mode 100644 index 0000000..5ee88c0 --- /dev/null +++ b/src/stories/Breadcrumbs/breadcrumb-item.stories.ts @@ -0,0 +1,29 @@ +import type { Meta, StoryObj } from "@storybook/web-components"; +import type { ZetaBreadcrumbItem } from "../../components/breadcrumbs/breadcrumb-item/breadcrumb-item.ts"; +import { html } from "lit"; +import "../../components/breadcrumbs/breadcrumb-item/breadcrumb-item"; + +const meta: Meta<ZetaBreadcrumbItem> = { + component: "zeta-breadcrumb-item", + title: "Breadcrumb", + + parameters: { + design: { + url: "https://www.figma.com/file/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?type=design&node-id=21265-26581&mode=design&t=xLGLqCoG43B0vRUv-4" + }, + status: { + type: "ready" + } + } +}; + +export default meta; + +export const BreadcrumbItem: StoryObj<ZetaBreadcrumbItem> = { + render: args => html` + <zeta-breadcrumb-item .href=${args.href}> + <zeta-icon slot="icon">star</zeta-icon> + Icon before with separator + </zeta-breadcrumb-item> + ` +}; diff --git a/src/stories/Breadcrumbs/breadcrumb.stories.ts b/src/stories/Breadcrumbs/breadcrumb.stories.ts new file mode 100644 index 0000000..e94232c --- /dev/null +++ b/src/stories/Breadcrumbs/breadcrumb.stories.ts @@ -0,0 +1,82 @@ +import type { Meta, StoryObj } from "@storybook/web-components"; +import type { ZetaBreadcrumb } from "../../components/breadcrumbs/breadcrumb.ts"; +import "../../components/breadcrumbs/breadcrumb-item/breadcrumb-item"; +import { html } from "lit"; +import "../../components/breadcrumbs/breadcrumb"; + +const meta: Meta<ZetaBreadcrumb> = { + component: "zeta-breadcrumb", + title: "Breadcrumb", + args: { + rounded: true + }, + argTypes: { + disabled: { table: { disable: true } }, + tabIndex: { table: { disable: true } } + }, + parameters: { + design: { + url: "https://www.figma.com/file/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?type=design&node-id=21265-26581&mode=design&t=xLGLqCoG43B0vRUv-4" + }, + status: { + type: "ready" + } + } +}; + +export default meta; + +export const Breadcrumb2Items: StoryObj<ZetaBreadcrumb> = { + render: args => html` + <zeta-breadcrumb .rounded=${args.rounded}> + <zeta-breadcrumb-item>Standard breadcrumb</zeta-breadcrumb-item> + <zeta-breadcrumb-item> + <zeta-icon slot="icon">star</zeta-icon> + Icon before with separator + </zeta-breadcrumb-item> + </zeta-breadcrumb> + ` +}; + +export const Breadcrumb3Items: StoryObj<ZetaBreadcrumb> = { + render: args => html` + <zeta-breadcrumb .rounded=${args.rounded}> + <zeta-breadcrumb-item> Standard breadcrumb </zeta-breadcrumb-item> + <zeta-breadcrumb-item> Text with separator </zeta-breadcrumb-item> + <zeta-breadcrumb-item> + <zeta-icon slot="icon">star</zeta-icon> + Icon before with separator + </zeta-breadcrumb-item> + </zeta-breadcrumb> + ` +}; + +export const Breadcrumb4Items: StoryObj<ZetaBreadcrumb> = { + render: args => html` + <zeta-breadcrumb .rounded=${args.rounded}> + <zeta-breadcrumb-item> Standard breadcrumb </zeta-breadcrumb-item> + <zeta-breadcrumb-item> Text with separator </zeta-breadcrumb-item> + <zeta-breadcrumb-item> Text with separator </zeta-breadcrumb-item> + <zeta-breadcrumb-item> + <zeta-icon .rounded=${args.rounded} slot="icon">star</zeta-icon> + Icon before with separator + </zeta-breadcrumb-item> + </zeta-breadcrumb> + ` +}; + +export const BreadcrumbTruncated: StoryObj<ZetaBreadcrumb> = { + render: args => html` + <zeta-breadcrumb maxItems=${4} .rounded=${args.rounded}> + <zeta-breadcrumb-item> Standard breadcrumb </zeta-breadcrumb-item> + <zeta-breadcrumb-item> Text with separator 1</zeta-breadcrumb-item> + <zeta-breadcrumb-item> Text with separator 2</zeta-breadcrumb-item> + <zeta-breadcrumb-item> Text with separator 3</zeta-breadcrumb-item> + <zeta-breadcrumb-item> Text with separator 4</zeta-breadcrumb-item> + <zeta-breadcrumb-item> + <zeta-icon slot="icon">star</zeta-icon> + Icon before with separator + </zeta-breadcrumb-item> + </zeta-breadcrumb> + ` +}; diff --git a/src/stories/Button Group/Docs.mdx b/src/stories/Button Group/Docs.mdx new file mode 100644 index 0000000..06c6389 --- /dev/null +++ b/src/stories/Button Group/Docs.mdx @@ -0,0 +1,16 @@ +import { Canvas, Meta, Description, ArgTypes, Title } from "@storybook/blocks"; +import * as ButtonGroupItem from "./button-group-item.stories.ts"; +import * as ButtonGroup from "./button-group.stories.ts"; + +<Meta of={ButtonGroup} title="Button Group" /> + +<Title /> +<Description of="zeta-button-group" /> +<Canvas of={ButtonGroup.ButtonGroup} /> +<ArgTypes of="zeta-button-group" /> + +# Button Group Item + +<Description of="zeta-button-group-item" /> +<Canvas of={ButtonGroupItem.GroupItem} /> +<ArgTypes of="zeta-button-group-item" /> diff --git a/src/stories/Button Group/button-group-item.stories.ts b/src/stories/Button Group/button-group-item.stories.ts new file mode 100644 index 0000000..2ebced5 --- /dev/null +++ b/src/stories/Button Group/button-group-item.stories.ts @@ -0,0 +1,74 @@ +import type { Meta, StoryObj } from "@storybook/web-components"; +import { html } from "lit"; +import { ZetaIconNameList } from "@zebra-fed/zeta-icons"; +import { ZetaButtonGroupItem } from "../../components/button-group/button-group-item/button-group-item.js"; + +const testFunc = () => console.log("Click"); + +const options = { testFunc, undefined }; + +const meta: Meta<ZetaButtonGroupItem> = { + component: "zeta-button-group-item", + title: "Button Group", + args: { + slot: "Label", + size: "medium", + rounded: true, + disabled: false, + name: "", + onclick: testFunc, + showDropdown: false + }, + argTypes: { + size: { + options: ["medium", "large"], + control: { + type: "select" + } + }, + icon: { + options: [null, ...ZetaIconNameList], + control: { + type: "select" + } + }, + onclick: { + options: Object.keys(options), + mapping: options, + control: { + type: "select", + labels: { + testFunc: "Dropdown on", + undefined: "Dropdown off" + } + } + } + }, + parameters: { + design: { + url: "https://www.figma.com/file/zzzpriTQpJKlW4gB5Fn3bF/Design-System-Sprint-3?type=design&node-id=23116-95148&mode=design&t=C1QjqPlEoal6a5PI-4" + }, + status: { + type: "inProgress" + } + } +}; + +export default meta; + +export const GroupItem: StoryObj = { + render: args => { + return html` + <zeta-button-group-item size=${args.size} .onclick=${args.onclick} .disabled=${args.disabled} .rounded=${args.rounded} ?showDropdown=${args.showDropdown}> + ${args.slot} ${args.icon && html`<zeta-icon slot="icon">${args.icon}</zeta-icon>`} + </zeta-button-group-item> + `; + } +}; + +export const GroupItemWithDropdown: StoryObj = { + args: { + showDropdown: true + }, + render: GroupItem.render +}; diff --git a/src/stories/Button Group/button-group.stories.ts b/src/stories/Button Group/button-group.stories.ts new file mode 100644 index 0000000..5f35986 --- /dev/null +++ b/src/stories/Button Group/button-group.stories.ts @@ -0,0 +1,71 @@ +import type { Meta, StoryObj } from "@storybook/web-components"; +import { ZetaButtonGroup } from "../../components/button-group/button-group.js"; +import "../../components/button-group/button-group-item/button-group-item.js"; +import { html } from "lit"; +import { ZetaIconNameList, type ZetaIconName } from "@zebra-fed/zeta-icons"; + +const testFunc = () => console.log("Click"); +const options = { testFunc, undefined }; + +const meta: Meta<ZetaButtonGroup> = { + component: "zeta-button-group", + title: "Button Group", + args: { + rounded: true, + size: "medium" + }, + argTypes: { + size: { + options: ["medium", "large"], + control: { type: "select" } + }, + onclick: { + options: Object.keys(options), + mapping: options, + control: { + type: "select", + labels: { + testFunc: "Dropdown on", + undefined: "Dropdown off" + } + } + }, + slot: { table: { disable: true } } + }, + parameters: { + design: { + url: "https://www.figma.com/file/zzzpriTQpJKlW4gB5Fn3bF/Design-System-Sprint-3?type=design&node-id=23116-95148&mode=design&t=C1QjqPlEoal6a5PI-4" + }, + status: { + type: "ready" + } + } +}; + +export default meta; + +export const ButtonGroup: StoryObj<ZetaButtonGroup & { count: number; showDropdown: boolean; icon?: ZetaIconName }> = { + args: { + icon: "star", + count: 4, + showDropdown: false + }, + argTypes: { + icon: { + options: [null, ...ZetaIconNameList], + control: { + type: "select" + } + } + }, + render: args => { + const array = Array(args.count).fill(0); + const mappedArray = array.map((_, index) => { + return html`<zeta-button-group-item .onclick=${args.onclick} ?showDropdown=${args.showDropdown}> + Label ${index} ${args.icon && html`<zeta-icon slot="icon">${args.icon}</zeta-icon>`} + </zeta-button-group-item> `; + }); + + return html` <zeta-button-group .rounded=${args.rounded} size=${args.size}> ${mappedArray.flat()} </zeta-button-group> `; + } +}; diff --git a/src/stories/Buttons/Docs.mdx b/src/stories/Buttons/Docs.mdx new file mode 100644 index 0000000..6f2f0f4 --- /dev/null +++ b/src/stories/Buttons/Docs.mdx @@ -0,0 +1,16 @@ +import { Canvas, Meta, Description, ArgTypes, Title } from "@storybook/blocks"; +import * as Button from "./button.stories"; +import * as IconButton from "./icon-button.stories"; + +<Meta of={Button} title="Buttons" /> + +<Title /> +<Description of="zeta-button" /> +<Canvas of={Button.Button} /> +<ArgTypes of="zeta-button" /> + +# Icon Button + +<Description of="zeta-icon-button" /> +<Canvas of={IconButton.IconButton} /> +<ArgTypes of="zeta-icon-button" /> diff --git a/src/stories/Buttons/button.stories.ts b/src/stories/Buttons/button.stories.ts new file mode 100644 index 0000000..a9c5247 --- /dev/null +++ b/src/stories/Buttons/button.stories.ts @@ -0,0 +1,68 @@ +import type { Meta, StoryObj } from "@storybook/web-components"; +import { html, nothing } from "lit"; +import { ZetaButton } from "../../components/button/button.js"; +import "../../components/button/base-button.js"; +import { ZetaIconNameList } from "@zebra-fed/zeta-icons"; +import { fn } from "@storybook/test"; +import "../../components/button/base-button.js"; +import "../../components/icon/icon.js"; + +const meta: Meta<ZetaButton> = { + title: "Buttons", + component: "zeta-button", + args: { + disabled: false, + flavor: "primary", + name: "", + rounded: true, + slot: "Button Name", + type: undefined, + value: "", + onclick: fn() + }, + parameters: { + design: { + url: "https://www.figma.com/file/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?type=design&node-id=23126-110945&mode=design&t=lGrwQ4pCwYESXz6b-4" + }, + status: { + type: "ready" + } + }, + argTypes: { + size: { + options: ["small", "medium", "large"], + control: { + type: "select" + } + }, + type: { + options: ["button", "submit", "reset"], + control: { + type: "select" + } + }, + flavor: { + options: ["primary", "secondary", "positive", "negative", "outline", "outline-subtle", "text"], + control: { + type: "select" + } + }, + leading: { + options: [null, ...ZetaIconNameList], + control: { type: "select" } + }, + trailing: { + options: [null, ...ZetaIconNameList], + control: { type: "select" } + } + } +}; +export default meta; + +export const Button: StoryObj<ZetaButton> = { + render: args => + html`<zeta-button size=${args.size} .disabled=${args.disabled} .rounded=${args.rounded} flavor=${args.flavor} @click=${args.onclick}> + ${args.leading && args.leading.length > 1 ? html`<zeta-icon slot="leadingIcon">${args.leading}</zeta-icon>` : nothing}${args.slot} + ${args.trailing && args.trailing.length > 1 ? html`<zeta-icon slot="trailingIcon">${args.trailing}</zeta-icon>` : nothing} + </zeta-button>` +}; diff --git a/src/stories/Buttons/icon-button.stories.ts b/src/stories/Buttons/icon-button.stories.ts new file mode 100644 index 0000000..0b8fea7 --- /dev/null +++ b/src/stories/Buttons/icon-button.stories.ts @@ -0,0 +1,72 @@ +import type { Meta, StoryObj } from "@storybook/web-components"; +import { ZetaIconNameList } from "@zebra-fed/zeta-icons"; +import { ZetaIconButton } from "../../components/button/icon-button/icon-button.js"; +import { spreadGenerator } from "../utils.js"; +import { html } from "lit"; +const spread = spreadGenerator(ZetaIconButton); + +const meta: Meta<ZetaIconButton> = { + title: "Buttons", + component: "zeta-icon-button", + args: { + disabled: false, + flavor: "primary", + name: "", + rounded: true, + slot: "star", + type: undefined, + value: "" + }, + parameters: { + design: { + url: "https://www.figma.com/file/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?type=design&node-id=23126-110314&mode=design&t=lGrwQ4pCwYESXz6b-4" + }, + status: { + type: "needsAttention" + } + }, + argTypes: { + slot: { + options: ZetaIconNameList, + control: { + type: "select" + }, + type: "string" + }, + size: { + options: ["small", "medium", "large"], + control: { + type: "select" + } + }, + type: { + options: ["button", "submit", "reset"], + control: { + type: "select" + } + }, + flavor: { + options: ["primary", "secondary", "positive", "negative", "outline", "outline-subtle", "text"], + control: { + type: "select" + } + } + } +}; + +export default meta; + +export const IconButton: StoryObj = { + render: ({ slot, ...args }) => { + return html` + <style> + :root { + ${args["--icon-button-color"] && `--icon-button-color: ${args["--icon-button-color"]}`} ; + ${args["--icon-button-icon-color"] && `--icon-button-icon-color: ${args["--icon-button-icon-color"]}`} ; + ${args["--icon-button-icon-color-disabled"] && `--icon-button-icon-color-disabled: ${args["--icon-button-icon-color-disabled"]}`} ; + } + </style> + <zeta-icon-button ${spread(args)}>${slot}</zeta-icon-button> + `; + } +}; diff --git a/src/stories/Card/Docs.mdx b/src/stories/Card/Docs.mdx new file mode 100644 index 0000000..c19c895 --- /dev/null +++ b/src/stories/Card/Docs.mdx @@ -0,0 +1,33 @@ +import { Canvas, Meta, Description, ArgTypes, Title } from "@storybook/blocks"; +import * as Card from "./card.stories"; +import * as Header from "./card-header.stories"; +import * as Body from "./card-body.stories"; +import * as Footer from "./card-footer.stories"; + +<Meta of={Card} title="Cards" /> + +<Title /> + +# Card + +<Description of="zeta-card" /> +<Canvas of={Card.CardWithHeader} /> +<ArgTypes of="zeta-card" /> + +# Header + +<Description of="zeta-card-header" /> +<Canvas of={Header.Header} /> +<ArgTypes of="zeta-card-header" /> + +# Body + +<Description of="zeta-card-body" /> +<Canvas of={Body.Body} /> +<ArgTypes of="zeta-card-body" /> + +# Footer + +<Description of="zeta-card-footer" /> +<Canvas of={Footer.FooterOneAction} /> +<ArgTypes of="zeta-card-footer" /> diff --git a/src/stories/Card/card-body.stories.ts b/src/stories/Card/card-body.stories.ts new file mode 100644 index 0000000..75804d2 --- /dev/null +++ b/src/stories/Card/card-body.stories.ts @@ -0,0 +1,25 @@ +import type { Meta, StoryObj } from "@storybook/web-components"; +import { html } from "lit"; +import { ZetaCardBody } from "../../components/card/card-body/card-body.js"; + +const meta: Meta<ZetaCardBody> = { + title: "Cards", + component: "zeta-card-body", + parameters: { + design: { + url: "https://www.figma.com/file/zzzpriTQpJKlW4gB5Fn3bF/Design-System-Sprint-3?type=design&node-id=1197-30562&mode=design&t=w4IloFPD61aGcU37-4" + }, + status: { + type: "needsAttention" + } + } +}; + +export default meta; + +export const Body: StoryObj<ZetaCardBody> = { + render: () => + html`<zeta-card-body> + Lorem ipsum dolor sit amet, conse ctetur adipiscing elit, sed do eiusm od tempor incididunt ut labore et do lore magna aliqua. + </zeta-card-body>` +}; diff --git a/src/stories/Card/card-footer.stories.ts b/src/stories/Card/card-footer.stories.ts new file mode 100644 index 0000000..823037f --- /dev/null +++ b/src/stories/Card/card-footer.stories.ts @@ -0,0 +1,41 @@ +import type { Meta, StoryObj } from "@storybook/web-components"; +import { html } from "lit"; +import { ZetaCardFooter } from "../../components/card/card-footer/card-footer.js"; +import "../../components/button/button.js"; + +const meta: Meta<ZetaCardFooter> = { + title: "Cards", + component: "zeta-card-footer", + parameters: { + design: { + url: "https://www.figma.com/file/zzzpriTQpJKlW4gB5Fn3bF/Design-System-Sprint-3?type=design&node-id=1197-29400&mode=design&t=w4IloFPD61aGcU37-4" + }, + status: { + type: "needsAttention" + } + } +}; + +export default meta; + +export const FooterOneAction: StoryObj<ZetaCardFooter> = { + render: () => + html`<zeta-card-footer> + <zeta-button>Button</zeta-button> + </zeta-card-footer>` +}; + +export const FooterTwoActions: StoryObj<ZetaCardFooter> = { + render: () => + html`<zeta-card-footer> + <zeta-button flavor="outline">Button</zeta-button> + <zeta-button>Button</zeta-button> + </zeta-card-footer>` +}; + +export const FooterOneActionLeft: StoryObj<ZetaCardFooter> = { + render: () => + html`<zeta-card-footer> + <zeta-button flavor="text">Button</zeta-button> + </zeta-card-footer>` +}; diff --git a/src/stories/Card/card-header.stories.ts b/src/stories/Card/card-header.stories.ts new file mode 100644 index 0000000..d83f673 --- /dev/null +++ b/src/stories/Card/card-header.stories.ts @@ -0,0 +1,51 @@ +import type { Meta, StoryObj } from "@storybook/web-components"; +import { html } from "lit"; +import { ZetaCardHeader } from "../../components/card/card-header/card-header.js"; +import { spreadGenerator } from "../utils.js"; +const spread = spreadGenerator(ZetaCardHeader); +import "../../components/avatar/avatar.js"; +import "../../components/button/icon-button/icon-button.js"; + +const meta: Meta<ZetaCardHeader> = { + component: "zeta-card-header", + title: "Cards", + args: { + headline: "Headline", + subHeadline: "Subhead" + }, + parameters: { + design: { + url: "https://www.figma.com/file/zzzpriTQpJKlW4gB5Fn3bF/Design-System-Sprint-3?type=design&node-id=1197-29423&mode=design&t=w4IloFPD61aGcU37-4" + }, + status: { + type: "needsAttention" + } + } +}; + +export default meta; + +export const Header: StoryObj = { + render: args => html` <zeta-card-header ${spread(args)}></zeta-card-header> ` +}; + +export const HeaderWithLeadingContent: StoryObj = { + render: args => html` <zeta-card-header ${spread(args)}><zeta-avatar slot="leading"></zeta-avatar></zeta-card-header> ` +}; + +export const HeaderWithTrailingContent: StoryObj = { + render: args => html` + <zeta-card-header ${spread(args)}> + <zeta-icon-button slot="trailing" flavor="text">more_vertical</zeta-icon-button> + </zeta-card-header> + ` +}; + +export const HeaderWithLeadingAndTrailingContent: StoryObj = { + render: args => html` + <zeta-card-header ${spread(args)}> + <zeta-avatar slot="leading"></zeta-avatar> + <zeta-icon-button slot="trailing" flavor="text">more_vertical</zeta-icon-button> + </zeta-card-header> + ` +}; diff --git a/src/stories/Card/card.stories.ts b/src/stories/Card/card.stories.ts new file mode 100644 index 0000000..1efa074 --- /dev/null +++ b/src/stories/Card/card.stories.ts @@ -0,0 +1,99 @@ +import type { Meta, StoryObj } from "@storybook/web-components"; +import { html } from "lit"; +import { styleMap } from "lit/directives/style-map.js"; +import { ZetaCard } from "../../components/card/card.js"; +import "../../components/card/card-body/card-body.js"; +import "../../components/card/card-footer/card-footer.js"; +import "../../components/card/card-header/card-header.js"; +import "../../components/button/icon-button/icon-button.js"; +import "../../components/button/button.js"; +import "../../components/icon/icon.js"; + +const meta: Meta<ZetaCard> = { + component: "zeta-card", + title: "Cards", + args: { + rounded: true + }, + parameters: { + design: { + url: "https://www.figma.com/file/zzzpriTQpJKlW4gB5Fn3bF/Design-System-Sprint-3?type=design&node-id=2379-71025&mode=design&t=w4IloFPD61aGcU37-4" + }, + status: { + type: "designPending" + } + } +}; + +export default meta; + +const placeholderStyle = styleMap({ + background: "var(--color-cool-30)", + display: "flex", + flex: "1", + alignItems: "center", + justifyContent: "center", + padding: "var(--spacing-8xl)", + "--icon-size": "48px", + "--icon-color": "var(--main-subtle)" +}); + +const placeholderImg = html`<div style=${placeholderStyle}> + <zeta-icon>image</zeta-icon> +</div>`; + +const cardBody = html`<zeta-card-body> + Lorem ipsum dolor sit amet, conse ctetur adipiscing elit, sed do eiusm od tempor incididunt ut labore et do lore magna aliqua. +</zeta-card-body>`; + +export const CardWithHeader: StoryObj<ZetaCard> = { + render: args => + html`<zeta-card .rounded=${args.rounded}> + <zeta-card-header headline="Headline" subHeadline="Subhead"> + <zeta-icon-button slot="trailing" flavor="text">more_vertical</zeta-icon-button> + </zeta-card-header> + ${placeholderImg} ${cardBody} + <zeta-card-footer> + <zeta-button>Button</zeta-button> + </zeta-card-footer> + </zeta-card> ` +}; + +export const CardWithTwoActions: StoryObj<ZetaCard> = { + render: args => + html`<zeta-card .rounded=${args.rounded}> + <zeta-card-header headline="Headline" subHeadline="Subhead"> + <zeta-icon-button slot="trailing" flavor="text">more_vertical</zeta-icon-button> + </zeta-card-header> + ${placeholderImg} ${cardBody} + <zeta-card-footer> + <zeta-button flavor="outline">Button</zeta-button> + <zeta-button>Button</zeta-button> + </zeta-card-footer> + </zeta-card> ` +}; + +export const CardWithOneActionLeft: StoryObj<ZetaCard> = { + render: args => + html`<zeta-card .rounded=${args.rounded}> + <zeta-card-header headline="Headline" subHeadline="Subhead"> + <zeta-icon-button slot="trailing" flavor="text">more_vertical</zeta-icon-button> + </zeta-card-header> + ${placeholderImg} ${cardBody} + <zeta-card-footer> + <zeta-button flavor="text">Button</zeta-button> + </zeta-card-footer> + </zeta-card> ` +}; + +export const CardWithTitle: StoryObj<ZetaCard> = { + render: args => + html`<zeta-card .rounded=${args.rounded}> + ${placeholderImg} + <zeta-card-header headline="Headline" subHeadline="Subhead"></zeta-card-header> + ${cardBody} + <zeta-card-footer> + <zeta-button>Button</zeta-button> + </zeta-card-footer> + </zeta-card> ` +}; diff --git a/src/stories/Chips/Docs.mdx b/src/stories/Chips/Docs.mdx new file mode 100644 index 0000000..fb402aa --- /dev/null +++ b/src/stories/Chips/Docs.mdx @@ -0,0 +1,33 @@ +import { Canvas, Meta, Description, ArgTypes, Stories, Title } from "@storybook/blocks"; +import * as AssistChip from "./assist-chip.stories"; +import * as FilterChip from "./filter-chip.stories"; +import * as InputChip from "./input-chip.stories"; +import * as StatusChip from "./status-chip.stories"; + +<Meta of={AssistChip} title="Chips" /> + +<Title /> + +# Assit Chip + +<Description of="zeta-assist-chip" /> +<Canvas of={AssistChip.AssistChip} /> +<ArgTypes of="zeta-assist-chip" /> + +# Filter Chip + +<Description of="zeta-filter-chip" /> +<Canvas of={FilterChip.FilterChip} /> +<ArgTypes of="zeta-filter-chip" /> + +# Filter Chip + +<Description of="zeta-input-chip" /> +<Canvas of={InputChip.InputChip} /> +<ArgTypes of="zeta-input-chip" /> + +# Status Chip + +<Description of="zeta-status-chip" /> +<Canvas of={StatusChip.StatusChip} /> +<ArgTypes of="zeta-status-chip" /> diff --git a/src/stories/Chips/assist-chip.stories.ts b/src/stories/Chips/assist-chip.stories.ts new file mode 100644 index 0000000..2631a9c --- /dev/null +++ b/src/stories/Chips/assist-chip.stories.ts @@ -0,0 +1,35 @@ +import type { Meta, StoryObj } from "@storybook/web-components"; +import { ZetaAssistChip } from "../../components/chips/assist-chip/assist-chip.js"; +import { html } from "lit"; +import { ZetaIconNameList } from "@zebra-fed/zeta-icons"; + +const meta: Meta<ZetaAssistChip> = { + component: "zeta-assist-chip", + title: "Chips", + args: { + rounded: true, + disabled: false, + icon: "star", + slot: "Label" + }, + argTypes: { + icon: { + options: ZetaIconNameList, + control: "select" + } + }, + parameters: { + design: { + url: "https://www.figma.com/file/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?type=design&node-id=21265-14215&mode=design&t=DUHsS5bjWB5UW1iG-4" + }, + status: { + type: "needsAttention" + } + } +}; + +export default meta; + +export const AssistChip: StoryObj<ZetaAssistChip> = { + render: args => html`<zeta-assist-chip ?rounded=${args.rounded} ?disabled=${args.disabled} .icon=${args.icon}>${args.slot}</zeta-assist-chip>` +}; diff --git a/src/stories/Chips/filter-chip.stories.ts b/src/stories/Chips/filter-chip.stories.ts new file mode 100644 index 0000000..e0f5357 --- /dev/null +++ b/src/stories/Chips/filter-chip.stories.ts @@ -0,0 +1,31 @@ +import type { Meta, StoryObj } from "@storybook/web-components"; +import { ZetaFilterChip } from "../../components/chips/filter-chip/filter-chip.js"; +import { html } from "lit"; + +const meta: Meta<ZetaFilterChip> = { + component: "zeta-filter-chip", + title: "Chips", + args: { + active: false, + rounded: false, + slot: "Chip", + disabled: false + }, + parameters: { + design: { + url: "https://www.figma.com/file/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?type=design&node-id=21265-14112&mode=design&t=DUHsS5bjWB5UW1iG-4" + }, + status: { + type: "needsAttention" + } + } +}; + +export default meta; + +export const FilterChip: StoryObj<ZetaFilterChip> = { + render: ({ slot, ...args }) => + html`<zeta-filter-chip ?disabled=${args.disabled} @change=${(e: CustomEvent) => console.log(e)} ?rounded=${args.rounded} ?active=${args.active} + >${slot}</zeta-filter-chip + >` +}; diff --git a/src/stories/Chips/input-chip.stories.ts b/src/stories/Chips/input-chip.stories.ts new file mode 100644 index 0000000..529580f --- /dev/null +++ b/src/stories/Chips/input-chip.stories.ts @@ -0,0 +1,30 @@ +import type { Meta, StoryObj } from "@storybook/web-components"; +import { ZetaInputChip } from "../../components/chips/input-chip/input-chip.js"; +import { html } from "lit"; +import "../../components/avatar/avatar.js"; + +const meta: Meta<ZetaInputChip> = { + title: "Chips", + component: "zeta-input-chip", + args: { + rounded: false, + slot: "Label" + }, + parameters: { + design: { + url: "https://www.figma.com/file/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?type=design&node-id=21265-2159&mode=design&t=DUHsS5bjWB5UW1iG-4" + }, + status: { + type: "ready" + } + } +}; + +export default meta; + +export const InputChip: StoryObj<ZetaInputChip> = { + render: args => + html`<zeta-input-chip ?rounded=${args.rounded} ?disabled=${args.disabled} + ><zeta-avatar size="xxxs"><img src="https://tinyurl.com/yn89fmc4"></img></zeta-avatar> ${args.slot}</zeta-input-chip + >` +}; diff --git a/src/stories/Chips/status-chip.stories.ts b/src/stories/Chips/status-chip.stories.ts new file mode 100644 index 0000000..678a60f --- /dev/null +++ b/src/stories/Chips/status-chip.stories.ts @@ -0,0 +1,24 @@ +import type { Meta, StoryObj } from "@storybook/web-components"; +import { ZetaStatusChip } from "../../components/chips/status-chip/status-chip.js"; + +const meta: Meta<ZetaStatusChip> = { + title: "Chips", + component: "zeta-status-chip", + args: { + text: "Input Custom", + rounded: true + }, + argTypes: {}, + parameters: { + design: { + url: "https://www.figma.com/file/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?type=design&node-id=21265-14282&mode=design&t=DUHsS5bjWB5UW1iG-4" + }, + status: { + type: "ready" + } + } +}; + +export default meta; + +export const StatusChip: StoryObj<ZetaStatusChip> = {}; diff --git a/src/stories/Dropdown/Docs.mdx b/src/stories/Dropdown/Docs.mdx new file mode 100644 index 0000000..9aca44b --- /dev/null +++ b/src/stories/Dropdown/Docs.mdx @@ -0,0 +1,151 @@ +import { Canvas, Meta, Description, ArgTypes, Title, Stories, Story, Source } from "@storybook/blocks"; +import * as DropdownMenuItem from "./dropdown-menu-item.stories"; +import * as DropdownMenuButton from "./dropdown-menu-button.stories"; +import * as Droppable from "./droppable.stories"; + +<Meta of={DropdownMenuButton} title="Dropdown" /> + +<Title /> + +# Dropdown Menu Button + +<Description of="zeta-dropdown-menu-button" /> +<div + style={{ + margin: "25px 30px 0", + padding: "40px 30px", + borderRadius: "4px", + background: "#FFFFFF", + boxShadow: "rgba(0, 0, 0, 0.10) 0 1px 3px 0", + border: "1px solid hsla(203, 50%, 30%, 0.15)" + }}> + <Story of={DropdownMenuButton.DropdownMenuButton} /> + +</div> + +Pass an array of objects to the `items` property. Each object must have a `label` and can optionally include an `icon` property, a `checked` boolean property to precheck one or more items (only check one if using radio buttons), and a `function` property which runs when that item is clicked. + +Use the `type` prop to define the type of dropdown menu button. The options are `text-dropdown`, `dropdown-radio`, and `dropdown-checkbox`. The default is `text-dropdown`. The `dropdown-checkbox` type allows for multiple items to be selected. The `dropdown-radio` type allows for only one item to be selected. + +Get the data from the dropdown menu button by wrapping it in a form and listening for the submit event. Use the `FormData` constructor to get the data from the form. + +As you use the dropdown menu button, think about whether you'd rather put the dropdown menu items in a prop or in a slot. Please let the development team know for future reference. + +<div + style={{ + margin: "0 15px", + }}> + ```jsx + const items = [ + { label: "Item 1", icon: "star"}, + { label: "Item 2", icon: "star"}, + { label: "Item 3", icon: "star"}, + ]; + + <form + id="form" + @submit=${(ev: Event) => { + ev.preventDefault(); + console.log("Submit", ev); + const data = new FormData(ev.target as HTMLFormElement); + console.log(Object.fromEntries(data)); + }} + @reset=${(e: Event) => { + console.error("Form reset", e); + }} + > + <zeta-dropdown-menu-button + items=${items} + type="dropdown-checkbox" + rounded=true + size="medium" + name=< ENTER NAME IF MORE THAN ONE DROPDOWN IS PRESENT > + flavor="primary"> + Dropdown Menu + </zeta-dropdown-menu-button> + + <button type="submit">Submit</button> + </form> + ``` + +</div> + +<ArgTypes of="zeta-dropdown-menu-button" /> + +--- + +--- + +--- + +# Droppable + +<Description of="zeta-droppable" /> + +Droppable is used by the dropdown menu button to display the dropdown menu items. However, it could be used independently or with another component as the anchor. + +<div + style={{ + margin: "25px 30px 0", + padding: "40px 30px", + borderRadius: "4px", + background: "#FFFFFF", + boxShadow: "rgba(0, 0, 0, 0.10) 0 1px 3px 0", + border: "1px solid hsla(203, 50%, 30%, 0.15)", + height: "280px" + }} +> + <Story of={Droppable.Droppable} /> +</div> + +<div + style={{ + margin: "0 15px", + }}> + ```jsx + <zeta-droppable rounded=true open=true > + <zeta-dropdown-menu-item icon="star" rounded=true > Menu Item </zeta-dropdown-menu-item> + <zeta-dropdown-menu-item icon="star" rounded=true > Menu Item </zeta-dropdown-menu-item> + <zeta-dropdown-menu-item icon="star" rounded=true > Menu Item </zeta-dropdown-menu-item> + </zeta-droppable> + ``` + +</div> + +<ArgTypes of="zeta-droppable" /> + +--- + +--- + +--- + +# Dropdown Menu Item + +<Description of="zeta-dropdown-menu-item" /> +<div + style={{ + margin: "25px 30px 0", + padding: "40px 30px", + borderRadius: "4px", + background: "#FFFFFF", + boxShadow: "rgba(0, 0, 0, 0.10) 0 1px 3px 0", + border: "1px solid hsla(203, 50%, 30%, 0.15)" + }} +> + <Story of={DropdownMenuItem.DropdownMenuItem} /> +</div> + +<div + style={{ + margin: "0 15px", + }}> + ```jsx + <zeta-dropdown-menu-item icon="star" rounded=true disabled=false > Menu Item </zeta-dropdown-menu-item> + +``` + +</div> + +<ArgTypes of="zeta-dropdown-menu-item" /> +``` diff --git a/src/stories/Dropdown/dropdown-menu-button.stories.ts b/src/stories/Dropdown/dropdown-menu-button.stories.ts new file mode 100644 index 0000000..9495860 --- /dev/null +++ b/src/stories/Dropdown/dropdown-menu-button.stories.ts @@ -0,0 +1,174 @@ +import type { Meta, StoryObj } from "@storybook/web-components"; +import { html } from "lit"; +import { ZetaDropdownMenuButton } from "../../components/dropdown/dropdown-menu/dropdown-menu-button.js"; + +const items1 = [ + { label: "Item 1", icon: "star" }, + { label: "Item 2", icon: "star" }, + { label: "Item 3", icon: "star" } +]; + +const items2 = [ + { label: "Item 1", icon: "star" }, + { label: "Item 2", icon: "star" }, + { label: "Item 3", icon: "star" } +]; + +const items3 = [ + { label: "Item 1", icon: "star", checked: true }, + { label: "Item 2", icon: "star" }, + { label: "Item 3", icon: "star" } +]; + +const staticArgTypes = { + items: { table: { disable: true } }, + type: { table: { disable: true } }, + name: { table: { disable: true } }, + autoComplete: { table: { disable: true } }, + autoCapitalize: { table: { disable: true } }, + id: { table: { disable: true } }, + required: { table: { disable: true } }, + value: { table: { disable: true } }, + checked: { table: { disable: true } }, + indeterminate: { table: { disable: true } }, + placeholder: { table: { disable: true } }, + readOnly: { table: { disable: true } }, + spellCheck: { table: { disable: true } }, + disabled: { table: { disable: true } }, + size: { table: { disable: true } }, + checkValidity: { table: { disable: true } }, + reportValidity: { table: { disable: true } }, + validity: { table: { disable: true } }, + validationMessage: { table: { disable: true } } +}; + +const meta: Meta<ZetaDropdownMenuButton> = { + component: "zeta-dropdown-menu-button", + title: "Dropdown", + args: { + rounded: true, + flavor: "primary", + open: false, + direction: undefined, + slot: "Dropdown Menu" + }, + argTypes: { + flavor: { + options: ["primary", "secondary", "positive", "negative", "outline", "outline-subtle", "text"], + control: { + type: "select" + } + }, + direction: { + options: ["left", "right", "bottom", "top", undefined], + control: { + type: "select" + } + } + }, + parameters: { + design: { + url: "https://www.figma.com/file/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?type=design&node-id=22391-10146&mode=design&t=DUHsS5bjWB5UW1iG-4" + }, + status: { + type: "ready" + } + } +}; + +export default meta; + +export const DropdownMenuButton: StoryObj<ZetaDropdownMenuButton> = { + argTypes: staticArgTypes, + render: args => html` + <div style="display: flex;"> + <div style="padding: 30px"> + <h4>Small - Default</h4> + <form + id="form" + @submit=${(ev: Event) => { + ev.preventDefault(); + console.log("Submit", ev); + const data = new FormData(ev.target as HTMLFormElement); + console.log(Object.fromEntries(data)); + }} + @reset=${(e: Event) => { + console.error("Form reset", e); + }} + > + <zeta-dropdown-menu-button + name="dropdown-menu1" + .size=${"small"} + ?rounded=${args.rounded} + .flavor=${args.flavor} + .type=${"text-dropdown"} + .items=${items1} + ?open=${args.open} + .direction=${args.direction} + > + ${args.slot} + </zeta-dropdown-menu-button> + <button type="submit">Submit</button> + </form> + </div> + <div style="padding: 30px"> + <h4>Medium - Checkboxes</h4> + <form + id="form" + @submit=${(ev: Event) => { + ev.preventDefault(); + console.log("Submit", ev); + const data = new FormData(ev.target as HTMLFormElement); + console.log(Object.fromEntries(data)); + }} + @reset=${(e: Event) => { + console.error("Form reset", e); + }} + > + <zeta-dropdown-menu-button + name="dropdown-menu2" + .size=${"medium"} + ?rounded=${args.rounded} + .flavor=${args.flavor} + .type=${"checkbox-dropdown"} + .items=${items2} + ?open=${args.open} + .direction=${args.direction} + > + ${args.slot} + </zeta-dropdown-menu-button> + <button type="submit">Submit</button> + </form> + </div> + <div style="padding: 30px"> + <h4>Large - Radios</h4> + <form + id="form" + @submit=${(ev: Event) => { + ev.preventDefault(); + console.log("Submit", ev); + const data = new FormData(ev.target as HTMLFormElement); + console.log(Object.fromEntries(data)); + }} + @reset=${(e: Event) => { + console.error("Form reset", e); + }} + > + <zeta-dropdown-menu-button + name="dropdown-menu3" + .size=${"large"} + ?rounded=${args.rounded} + .flavor=${args.flavor} + .type=${"radio-dropdown"} + .items=${items3} + ?open=${args.open} + .direction=${args.direction} + > + ${args.slot} + </zeta-dropdown-menu-button> + <button type="submit">Submit</button> + </form> + </div> + </div> + ` +}; diff --git a/src/stories/Dropdown/dropdown-menu-item.stories.ts b/src/stories/Dropdown/dropdown-menu-item.stories.ts new file mode 100644 index 0000000..7568b58 --- /dev/null +++ b/src/stories/Dropdown/dropdown-menu-item.stories.ts @@ -0,0 +1,40 @@ +import type { Meta, StoryObj } from "@storybook/web-components"; +import { html } from "lit"; +import { ZetaDropdownMenuItem } from "../../components/dropdown/menu-item/dropdown-menu-item.js"; +import { ZetaIconNameList, type ZetaIconName } from "@zebra-fed/zeta-icons"; + +const meta: Meta<ZetaDropdownMenuItem & { icon: ZetaIconName }> = { + component: "zeta-dropdown-menu-item", + title: "Dropdown", + args: { + rounded: true, + disabled: false, + icon: "star", + slot: "Menu Item" + }, + argTypes: { + icon: { + options: ZetaIconNameList, + control: { type: "select" } + }, + tabIndex: { + table: { disable: true } + } + }, + parameters: { + design: { + url: "https://www.figma.com/file/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?type=design&node-id=22391-10146&mode=design&t=DUHsS5bjWB5UW1iG-4" + }, + status: { type: "ready" } + } +}; + +export default meta; + +export const DropdownMenuItem: StoryObj<ZetaDropdownMenuItem & { icon: ZetaIconName }> = { + render: args => + html`<zeta-dropdown-menu-item .rounded=${args.rounded} .disabled=${args.disabled}> + <zeta-icon .rounded=${args.rounded} slot="icon">${args.icon}</zeta-icon> + ${args.slot} + </zeta-dropdown-menu-item>` +}; diff --git a/src/stories/Dropdown/droppable.stories.ts b/src/stories/Dropdown/droppable.stories.ts new file mode 100644 index 0000000..9dec82d --- /dev/null +++ b/src/stories/Dropdown/droppable.stories.ts @@ -0,0 +1,44 @@ +import type { Meta, StoryObj } from "@storybook/web-components"; +import { html } from "lit"; +import { ZetaDroppable } from "../../components/dropdown/droppable.js"; +import "../../components/dropdown/menu-item/dropdown-menu-item.js"; + +const meta: Meta<ZetaDroppable> = { + component: "zeta-droppable", + title: "Dropdown", + args: { + rounded: true, + open: true + }, + argTypes: { + matchParentWidth: { table: { disable: true } } + }, + parameters: { + design: { + url: "https://www.figma.com/file/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?type=design&node-id=22391-10146&mode=design&t=DUHsS5bjWB5UW1iG-4" + }, + status: { + type: "ready" + } + } +}; + +export default meta; + +export const Droppable: StoryObj<ZetaDroppable> = { + render: args => + html`<div style="display: flex; align-items: center; gap: 100px; flex-wrap: wrap; "> + <div> + <h4>No Items</h4> + <zeta-droppable ?rounded=${args.rounded} ?open=${args.open}> </zeta-droppable> + </div> + <div> + <h4>With Dropdown Menu Items</h4> + <zeta-droppable ?rounded=${args.rounded} ?open=${args.open}> + <zeta-dropdown-menu-item ?rounded=${args.rounded}><zeta-icon slot="icon">star</zeta-icon> Menu Item </zeta-dropdown-menu-item> + <zeta-dropdown-menu-item ?rounded=${args.rounded}><zeta-icon slot="icon">star</zeta-icon> Menu Item </zeta-dropdown-menu-item> + <zeta-dropdown-menu-item ?rounded=${args.rounded}><zeta-icon slot="icon">star</zeta-icon> Menu Item </zeta-dropdown-menu-item> + </zeta-droppable> + </div> + </div>` +}; diff --git a/src/stories/Fabs/Docs.mdx b/src/stories/Fabs/Docs.mdx new file mode 100644 index 0000000..12c3fd9 --- /dev/null +++ b/src/stories/Fabs/Docs.mdx @@ -0,0 +1,28 @@ +import { Canvas, Meta, Description, ArgTypes, Title, Controls } from "@storybook/blocks"; +import * as Fab from "./fab.stories"; + +<Meta of={Fab} title="Fabs" /> + +<Title /> +<Description of="zeta-fab" /> + +## Fab + +<Canvas of={Fab.Fab} /> + +## Extended Fab + +<Canvas of={Fab.ExtendedFab} /> + +<div style={{ display: "flex", flexWrap: "wrap", gap: "0 50px" }}> + <div style={{ minWidth: "300px", flex: "1" }}> + ## Secondary Flavor + <Canvas of={Fab.FabSecondary} /> + </div> + <div style={{ minWidth: "300px", flex: "1" }}> + ## Inverse Flavor + <Canvas of={Fab.FabInverse} /> + </div> +</div> + +<ArgTypes of={Fab} /> diff --git a/src/stories/Fabs/fab.stories.ts b/src/stories/Fabs/fab.stories.ts new file mode 100644 index 0000000..5b54085 --- /dev/null +++ b/src/stories/Fabs/fab.stories.ts @@ -0,0 +1,149 @@ +import type { Meta, StoryObj } from "@storybook/web-components"; +import { html } from "lit"; +import { ZetaFab } from "../../components/fab/fab.js"; +import "../../components/icon/icon.js"; +import { ifDefined } from "lit/directives/if-defined.js"; +import { ZetaIconNameList } from "@zebra-fed/zeta-icons"; + +const staticArgTypes = { + disabled: { table: { disable: true } }, + flavor: { table: { disable: true } }, + name: { table: { disable: true } }, + slot: { table: { disable: true } }, + type: { table: { disable: true } }, + value: { table: { disable: true } }, + label: { table: { disable: true } }, + round: { table: { disable: true } }, + size: { table: { disable: true } }, + extended: { table: { disable: true } } +}; + +const meta: Meta<ZetaFab> = { + component: "zeta-fab", + title: "Fabs", + args: { + disabled: false, + flavor: "primary", + name: "", + slot: "add", + type: undefined, + value: "", + label: "Label", + round: true, + size: "small", + extended: false + }, + parameters: { + design: { + url: "https://www.figma.com/file/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?type=design&node-id=21816-4283&m=dev&t=lGrwQ4pCwYESXz6b-4" + }, + status: { + type: "ready" + } + }, + argTypes: { + rounded: { + table: { disable: true } + }, + tabIndex: { + table: { disable: true } + }, + slot: { + options: ZetaIconNameList, + control: { type: "select" } + }, + size: { + options: ["small", "large"], + control: { + type: "inline-radio" + } + }, + type: { + options: ["button", "submit", "reset"], + control: { + type: "select" + } + }, + flavor: { + options: ["primary", "secondary", "inverse"], + control: { + type: "inline-radio" + }, + description: + "The flavor of the button. \n * Primary - blue background. \n * Secondary - yellow background. \n * Inverse - black/white background. \n\n Ignore options below." + }, + round: { + options: ["full", true, false], + control: { + type: "inline-radio" + } + } + } +}; + +export default meta; + +export const Fab: StoryObj<ZetaFab> = { + render: args => + html`<zeta-fab + .extended=${args.extended} + label=${args.label} + size=${args.size} + .disabled=${args.disabled} + .round=${args.round} + flavor=${args.flavor} + name=${ifDefined(args.name)} + value=${ifDefined(args.value)} + type=${ifDefined(args.type)} + > + ${args.slot} + </zeta-fab> ` +}; + +export const ExtendedFab: StoryObj<ZetaFab> = { + args: { + extended: true + }, + argTypes: { + size: { table: { disable: true } } + }, + render: args => + html`<zeta-fab + .extended=${args.extended} + label=${args.label} + .disabled=${args.disabled} + .round=${args.round} + flavor=${args.flavor} + name=${ifDefined(args.name)} + value=${ifDefined(args.value)} + type=${ifDefined(args.type)} + > + ${args.slot} + </zeta-fab> ` +}; + +export const FabSecondary: StoryObj<ZetaFab> = { + argTypes: staticArgTypes, + render: args => + html`<div style="display: flex; justify-content: space-around;flex-wrap: wrap; gap: 30px;"> + <zeta-fab .extended=${args.extended} label=${args.label} size=${args.size} .disabled=${args.disabled} .round=${args.round} flavor="secondary"> + ${args.slot} + </zeta-fab> + <zeta-fab ?extended=${true} label=${args.label} size=${args.size} .disabled=${args.disabled} .round=${args.round} flavor="secondary"> + ${args.slot} + </zeta-fab> + </div> ` +}; + +export const FabInverse: StoryObj<ZetaFab> = { + argTypes: staticArgTypes, + render: args => + html`<div style="display: flex; justify-content: space-around;flex-wrap: wrap; gap: 30px;"> + <zeta-fab .extended=${args.extended} label=${args.label} size=${args.size} .disabled=${args.disabled} .round=${args.round} flavor="inverse"> + ${args.slot} + </zeta-fab> + <zeta-fab ?extended=${true} label=${args.label} size=${args.size} .disabled=${args.disabled} .round=${args.round} flavor="inverse"> + ${args.slot} + </zeta-fab> + </div> ` +}; diff --git a/src/stories/File Upload/Docs.mdx b/src/stories/File Upload/Docs.mdx new file mode 100644 index 0000000..d7ddf63 --- /dev/null +++ b/src/stories/File Upload/Docs.mdx @@ -0,0 +1,25 @@ +import { Canvas, Meta, Description, ArgTypes, Title } from "@storybook/blocks"; +import * as ZetaFileUpload from "./file-upload.stories.ts"; +import * as ZetaUploadItem from "./upload-item.stories.ts"; + +<Meta of={ZetaFileUpload} /> + +<Title /> +<Canvas of={ZetaFileUpload.FileUpload} /> + +<Description of="zeta-file-upload" /> +<ArgTypes of={ZetaFileUpload} /> + +# Upload Item + +<Canvas of={ZetaUploadItem.UploadItem} /> +<Description of="zeta-upload-item" /> +<ArgTypes of={ZetaUploadItem.UploadItem} /> + +## Completed Upload Item + +<Canvas of={ZetaUploadItem.Completed} /> + +## Failed Upload Item + +<Canvas of={ZetaUploadItem.Error} /> diff --git a/src/stories/File Upload/file-upload.stories.ts b/src/stories/File Upload/file-upload.stories.ts new file mode 100644 index 0000000..9f30274 --- /dev/null +++ b/src/stories/File Upload/file-upload.stories.ts @@ -0,0 +1,28 @@ +import type { Meta, StoryObj } from "@storybook/web-components"; +import { ZetaFileUpload } from "../../components/file-upload/file-upload.js"; + +const meta: Meta<ZetaFileUpload> = { + component: "zeta-file-upload", + title: "File Upload", + args: { + rounded: true, + headline: "Drop files here to upload", + caption: "Supports: JPG, JPEG2000, PNG. Max file size 100mb", + multiple: true, + accept: "", + name: "", + active: false, + error: false + }, + parameters: { + design: { + url: "https://www.figma.com/file/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?type=design&node-id=898-10794&mode=design&t=DUHsS5bjWB5UW1iG-4" + }, + status: { + type: "ready" + } + } +}; +export default meta; + +export const FileUpload: StoryObj<ZetaFileUpload> = {}; diff --git a/src/stories/File Upload/upload-item.stories.ts b/src/stories/File Upload/upload-item.stories.ts new file mode 100644 index 0000000..1732fc0 --- /dev/null +++ b/src/stories/File Upload/upload-item.stories.ts @@ -0,0 +1,64 @@ +import type { Meta, StoryObj } from "@storybook/web-components"; +import type { ZetaUploadItem } from "../../components/upload-item/upload-item"; +import "../../components/upload-item/upload-item.js"; + +type UploadItemStory = ZetaUploadItem & { slot: string; subtitle: string; leading: string }; + +const meta: Meta<UploadItemStory> = { + component: "zeta-upload-item", + title: "File Upload", + args: { + flavor: "default", + rounded: true, + slot: "filename.jpg", + subtitle: "4.6MB of 5.7MB", + leading: "<zeta-icon>image</zeta-icon>", + progress: 75 + }, + argTypes: { + flavor: { + options: ["default", "completed", "error"], + control: { + type: "select" + } + } + }, + parameters: { + design: { + url: "https://www.figma.com/design/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?node-id=23753-10089&t=8Ym0ztJUuB4iD83l-4" + }, + status: { + type: "ready" + } + } +}; + +export default meta; + +export const UploadItem: StoryObj<UploadItemStory> = {}; + +export const Completed: StoryObj<UploadItemStory> = { + args: { + flavor: "completed", + slot: "filename.jpg", + subtitle: "Upload complete - 5.7MB" + }, + argTypes: { + progress: { table: { disable: true } }, + flavor: { table: { disable: true } }, + leading: { table: { disable: true } } + } +}; + +export const Error: StoryObj<UploadItemStory> = { + args: { + flavor: "error", + slot: "filename.jpg", + subtitle: "File exceeds limit." + }, + argTypes: { + progress: { table: { disable: true } }, + flavor: { table: { disable: true } }, + leading: { table: { disable: true } } + } +}; diff --git a/src/stories/Form/form.stories.ts b/src/stories/Form/form.stories.ts new file mode 100644 index 0000000..e1e11a5 --- /dev/null +++ b/src/stories/Form/form.stories.ts @@ -0,0 +1,95 @@ +import type { Meta, StoryObj } from "@storybook/web-components"; +import { html } from "lit"; +import type { FormEvent } from "react"; +import "../../components/text-input/text-input.js"; +import "../../components/button/button.js"; +import "../../components/switch/switch.js"; +import { styleMap } from "lit/directives/style-map.js"; +import "../../components/checkbox/checkbox"; +import "../../components/radio-button/radio-button"; +import "../../components/slider/slider-input-field/slider-input-field.js"; +import "../../components/slider/range-selector/range-selector.js"; + +const meta: Meta = { + title: "Form" +}; + +export default meta; + +export const Form: StoryObj = { + render: () => html` + <h1>Zeta Form</h1> + <form + style=${styleMap({ + display: "flex", + flexDirection: "column", + width: "fit-content", + maxWidth: "100%", + gap: "var(--spacing-large)" + })} + id="form" + @submit=${(e: FormEvent) => { + e.preventDefault(); + const data = new FormData(e.target as HTMLFormElement); + console.log(Object.fromEntries(data)); + }} + @reset=${(e: Event) => { + console.error("Form reset", e); + }} + > + <div style=${columnStyle}> + <h4 style=${subheadingStyle}>Inputs</h4> + <div style=${rowStyle}> + <div style=${columnStyle}> + <zeta-text-input id="zeta-input" name="zeta-input" placeholder="Placeholder" hintText="Hint text" value="Default value">Text Input</zeta-text-input> + <zeta-text-input id="zeta-date-input" name="zeta-date-input" placeholder="Placeholder" hintText="Hint text" type="date">Date Input</zeta-text-input> + <zeta-text-input id="zeta-time-input" name="zeta-time-input" placeholder="Placeholder" hintText="Hint text" type="time">Time Input</zeta-text-input> + </div> + <div style=${columnStyle}> + <zeta-text-input id="zeta-password-input" name="zeta-password-input" placeholder="Placeholder" hintText="Hint text" type="password" + >Password Input</zeta-text-input + > + <zeta-text-input id="zeta-text-area" name="zeta-text-area" placeholder="Placeholder" hintText="Hint text" type="textarea" + >Text Area</zeta-text-input + > + </div> + </div> + </div> + + <zeta-slider-input-field name="zeta-slider-input-field"></zeta-slider-input-field> + + <zeta-range-selector rounded name="zeta-range-selector"></zeta-range-selector> + + <zeta-switch id="switch" name="my-zeta-switch" checked>Switch</zeta-switch> + <div style=${rowStyle}> + <div style=${columnStyle}> + <h4 style=${subheadingStyle}>Checkboxes</h4> + <zeta-checkbox id="chkbx-1" name="zeta-checkbox-1" value="val">Checkbox 1</zeta-checkbox> + <zeta-checkbox name="zeta-checkbox-2">Checkbox 2</zeta-checkbox> + <zeta-checkbox name="zeta-checkbox-3">Checkbox 3</zeta-checkbox> + </div> + <fieldset style=${styleMap({ display: "flex", flexDirection: "column", gap: "var(--spacing-small)", flex: 1, margin: 0, borderWidth: 0, padding: 0 })}> + <h4 style=${subheadingStyle}>Radio Buttons WORK IN PROGRESS</h4> + <zeta-radio-button name="zeta-radio-button-1" value="val">Radio 1</zeta-radio-button> + <zeta-radio-button name="zeta-radio-button-1">Radio 2</zeta-radio-button> + <zeta-radio-button name="zeta-radio-button-1" value="notVal">Radio 3</zeta-radio-button> + </fieldset> + </div> + + <div + style=${styleMap({ + display: "flex", + justifyContent: "flex-end", + gap: "8px" + })} + > + <zeta-button type="reset" flavor="outline">Reset</zeta-button> + <zeta-button type="submit">Submit</zeta-button> + </div> + </form> + ` +}; + +const columnStyle = styleMap({ display: "flex", flexDirection: "column", gap: "var(--spacing-small)", flex: 1 }); +const rowStyle = styleMap({ display: "flex", gap: "var(--spacing-large)", flex: 1 }); +const subheadingStyle = styleMap({ margin: 0 }); diff --git a/src/stories/Grid Menu Item/grid-menu-item.stories.ts b/src/stories/Grid Menu Item/grid-menu-item.stories.ts new file mode 100644 index 0000000..08e5de2 --- /dev/null +++ b/src/stories/Grid Menu Item/grid-menu-item.stories.ts @@ -0,0 +1,53 @@ +import type { Meta, StoryObj } from "@storybook/web-components"; +import { html } from "lit"; +import { ZetaIconNameList, type ZetaIconName } from "@zebra-fed/zeta-icons"; +import type { ZetaGridMenuItem } from "../../components/grid-menu-item/grid-menu-item.js"; +import "../../components/grid-menu-item/grid-menu-item"; +import "../../components/badges/indicators/indicators.js"; +import "../../components/icon/icon.js"; + +const meta: Meta<ZetaGridMenuItem | { icon?: ZetaIconName }> = { + component: "zeta-grid-menu-item", + title: "Grid Menu Item", + tags: ["autodocs"], + args: { + rounded: true, + active: false, + icon: "star", + slot: "Label", + notificationValue: "" + }, + argTypes: { + icon: { + options: ZetaIconNameList, + control: { + type: "select" + } + }, + notificationValue: { + options: [true, false, "1", "2", "3", "4", "5", "6", "7", "8", "9", "+"], + control: { + type: "select" + } + } + }, + parameters: { + design: { + url: "https://www.figma.com/file/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?type=design&node-id=21186-41419&mode=design&t=DUHsS5bjWB5UW1iG-4" + }, + status: { + type: "ready" + } + } +}; + +export default meta; + +export const Item: StoryObj<ZetaGridMenuItem & { icon: ZetaIconName }> = { + render: args => { + return html`<zeta-grid-menu-item .rounded=${args.rounded} .active=${args.active} .notificationValue=${args.notificationValue}> + <zeta-icon slot="icon">${args.icon}</zeta-icon> + ${args.slot} + </zeta-grid-menu-item>`; + } +}; diff --git a/src/stories/In Page Banner/Docs.mdx b/src/stories/In Page Banner/Docs.mdx new file mode 100644 index 0000000..b7846eb --- /dev/null +++ b/src/stories/In Page Banner/Docs.mdx @@ -0,0 +1,53 @@ +import { Canvas, Meta, Description, ArgTypes, Title, Stories, Story, Source } from "@storybook/blocks"; +import * as InPageBanner from "./in-page-banner.stories"; + +<Meta of={InPageBanner} title="In Page Banner" /> + +<Title /> +<Description of="zeta-in-page-banner" /> +<Canvas of={InPageBanner.Banner} /> +<ArgTypes of="zeta-in-page-banner" /> + +--- + +## Banner Single Action + +To implement a banner with a single action, insert a `<zeta-button>` element in the `leading-action` slot. + +> Note: Current build has incorrect colors on button. This will be resolved by UX-1146 + +<Canvas of={InPageBanner.BannerSingleAction} /> + +## Banner Dual Action + +To implement a banner with dual actions, insert `<zeta-button>` elements in the `leading-action` and `trailing-action` slots respectively. + +<Canvas of={InPageBanner.BannerDualAction} /> + +## Banner With Content + +To add more content to your banner, insert the HTML elements as the banner’s children / in the default slot. + +``` +<zeta-in-page-banner> + INSERT YOUR CONTENT HERE +</zeta-in-page-banner>` +``` + +<Canvas of={InPageBanner.BannerWithImage} /> + +## Note: + +`rounded` does not effect indirect children of the default slot. If you want rounded corners on images or other elements, insure they are direct children of the default slot or manually apply rounded corners with CSS. + +<Canvas of={InPageBanner.BannerWithContent} /> + +## Banner Constrained Width + +To limit a banner’s width, wrap it in a parent element with a specified width. + +For example, use `<div style="max-width: 500px">` to set the banner’s width to 500px. + +Any image inside the image slot will adjust to it's parent's width, making it responsive. + +<Canvas of={InPageBanner.BannerConstrainedWidth} /> diff --git a/src/stories/In Page Banner/in-page-banner.stories.ts b/src/stories/In Page Banner/in-page-banner.stories.ts new file mode 100644 index 0000000..54e62ab --- /dev/null +++ b/src/stories/In Page Banner/in-page-banner.stories.ts @@ -0,0 +1,173 @@ +import type { Meta, StoryObj } from "@storybook/web-components"; +import { html } from "lit"; +import { ZetaInPageBanner } from "../../components/in-page-banner/in-page-banner.js"; +import "../../components/button/button.js"; +import { spreadGenerator } from ".././utils.js"; +import { unsafeHTML } from "lit/directives/unsafe-html.js"; +const spread = spreadGenerator(ZetaInPageBanner); + +const meta: Meta<ZetaInPageBanner> = { + component: "zeta-in-page-banner", + title: "In Page Banner", + args: { + title: "Banner title", + slot: "Lorem ipsum dolor sit amet, conse ctetur cididunt ut labore et do lore magna aliqua.", + rounded: true, + status: "default" + }, + argTypes: { + status: { + options: ["default", "info", "positive", "warning", "negative"], + control: { + type: "select" + } + } + }, + parameters: { + design: { + url: "https://www.figma.com/file/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?type=design&node-id=21156-27071&mode=design&t=DUHsS5bjWB5UW1iG-4" + }, + status: { + type: "ready" + } + } +}; + +export default meta; + +export const Banner: StoryObj = { + argTypes: { + action: { table: { disable: true } } + } +}; + +export const BannerSingleAction: StoryObj = { + argTypes: { + action: { table: { disable: true } } + }, + render: args => + html`<zeta-in-page-banner ${spread(args)}> + ${args.slot} + <zeta-button slot="action">Button</zeta-button> + </zeta-in-page-banner>` +}; + +export const BannerDualAction: StoryObj = { + argTypes: { + action: { table: { disable: true } } + }, + render: args => + html`<zeta-in-page-banner ${spread(args)}> + ${args.slot} + <zeta-button slot="action">Button</zeta-button> + <zeta-button slot="action">Button 2</zeta-button> + </zeta-in-page-banner>` +}; + +export const BannerWithImage: StoryObj = { + args: { + imageX: 450, + imageY: 330 + }, + argTypes: { + imageX: { + control: { + type: "range", + min: 50, + max: 2000, + step: 50 + } + }, + imageY: { + control: { + type: "range", + min: 50, + max: 2000, + step: 50 + } + }, + slot: { table: { disable: true } }, + action: { table: { disable: true } } + }, + render: args => + html` <zeta-in-page-banner title=${args.title} ?rounded=${args.rounded} status=${args.status}> + <img src=${"https://placehold.co/" + args.imageX + "x" + args.imageY + "/png"} /> + <zeta-button slot="action">Button</zeta-button> + </zeta-in-page-banner>` +}; + +export const BannerWithContent: StoryObj = { + args: { + constrainedWidth: false, + slot: `<h1 style="text-decoration: underline">Add more content</h1> +<h3>Add even more</h3> +<p>And even more</p> +<ol style="font-style: italic"> + <li>Add</li> + <li>a</li> + <li>List</li> +</ol> +<h4 style="margin-bottom: 8px;">Use normal HTML and CSS inside the banner</h4> +<div style="width: 250px"> + <img src=${"https://placehold.co/250x200/png"} /> +</div>` + }, + argTypes: { + action: { table: { disable: true } } + }, + render: args => { + const renderedBanner = html` <zeta-in-page-banner title=${args.title} ?rounded=${args.rounded} status=${args.status}> + ${unsafeHTML(`${args.slot}`)} + <zeta-button slot="action">Button</zeta-button> + </zeta-in-page-banner>`; + + const renderedBannerInContainer = html`<div style="max-width: 500px;">${renderedBanner}</div>`; + + if (args.constrainedWidth) { + return renderedBannerInContainer; + } else { + return renderedBanner; + } + } +}; + +export const BannerConstrainedWidth: StoryObj = { + args: { + imageX: 450, + imageY: 330, + constrainedWidth: true + }, + argTypes: { + imageX: { + control: { + type: "range", + min: 50, + max: 2000, + step: 50 + } + }, + imageY: { + control: { + type: "range", + min: 50, + max: 2000, + step: 50 + }, + action: { table: { disable: true }, slot: { table: { disable: true } } } + } + }, + render: args => { + const renderedBanner = html`<zeta-in-page-banner ${spread(args)}> + <img src=${"https://placehold.co/" + args.imageX + "x" + args.imageY + "/png"} /> + <zeta-button flavor="positive" slot="action">Button</zeta-button> + </zeta-in-page-banner>`; + + const renderedBannerInContainer = html`<div style="max-width: 500px;">${renderedBanner}</div>`; + + if (args.constrainedWidth) { + return renderedBannerInContainer; + } else { + return renderedBanner; + } + } +}; diff --git a/src/stories/Introduction.mdx b/src/stories/Introduction.mdx new file mode 100644 index 0000000..4733af5 --- /dev/null +++ b/src/stories/Introduction.mdx @@ -0,0 +1,4 @@ +import Intro from "../../README.md?raw"; +import { Markdown } from "@storybook/blocks"; + +<Markdown>{Intro}</Markdown> diff --git a/src/stories/List/Docs.mdx b/src/stories/List/Docs.mdx new file mode 100644 index 0000000..8137f42 --- /dev/null +++ b/src/stories/List/Docs.mdx @@ -0,0 +1,16 @@ +import { Canvas, Meta, Description, ArgTypes, Title } from "@storybook/blocks"; +import * as List from "./list.stories"; +import * as Item from "./list-item.stories"; + +<Meta of={List} title="List" /> + +<Title /> +<Description of="zeta-list" /> +<Canvas of={List.List} /> +<ArgTypes of="zeta-list" /> + +# List Item + +<Description of="zeta-list-item" /> +<Canvas of={Item.ListItem} /> +<ArgTypes of="zeta-list-item" /> diff --git a/src/stories/List/list-item.stories.ts b/src/stories/List/list-item.stories.ts new file mode 100644 index 0000000..68058e2 --- /dev/null +++ b/src/stories/List/list-item.stories.ts @@ -0,0 +1,47 @@ +import type { Meta, StoryObj } from "@storybook/web-components"; +import { html } from "lit"; +import { ZetaListItem } from "../../components/list/list-item/list-item.js"; +import "../../components/avatar/avatar.js"; +import "../../components/icon/icon.js"; +import "../../components/checkbox/checkbox.js"; +import { ifDefined } from "lit/directives/if-defined.js"; + +const meta: Meta<ZetaListItem> = { + component: "zeta-list-item", + title: "List", + args: { + headline: "Headline" + }, + parameters: { + design: { + url: "https://www.figma.com/file/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?type=design&node-id=19858-18383&mode=design&t=DUHsS5bjWB5UW1iG-4" + }, + status: { + type: "ready" + } + } +}; + +export default meta; + +export const ListItem: StoryObj<ZetaListItem> = {}; + +export const ListItemWithIconLeft: StoryObj<ZetaListItem> = { + render: args => html`<zeta-list-item headline=${ifDefined(args.headline)}><zeta-icon slot="leading">star</zeta-icon></zeta-list-item>` +}; + +export const ListItemWithIconRight: StoryObj<ZetaListItem> = { + render: args => html`<zeta-list-item headline=${ifDefined(args.headline)}><zeta-icon slot="trailing">star</zeta-icon></zeta-list-item>` +}; + +export const ListItemWithAction: StoryObj<ZetaListItem> = { + render: args => html`<zeta-list-item headline=${ifDefined(args.headline)}><zeta-checkbox slot="trailing"></zeta-checkbox></zeta-list-item>` +}; + +export const ListItemWithAvatarAndAction: StoryObj<ZetaListItem> = { + render: args => + html`<zeta-list-item headline=${ifDefined(args.headline)}> + <zeta-avatar slot="leading" size="sm"></zeta-avatar> + <zeta-checkbox slot="trailing"></zeta-checkbox> + </zeta-list-item>` +}; diff --git a/src/stories/List/list.stories.ts b/src/stories/List/list.stories.ts new file mode 100644 index 0000000..092b42e --- /dev/null +++ b/src/stories/List/list.stories.ts @@ -0,0 +1,31 @@ +import type { Meta, StoryObj } from "@storybook/web-components"; +import { html } from "lit"; +import { ZetaList } from "../../components/list/list.js"; +import "../../components/list/list-item/list-item.js"; + +const meta: Meta<ZetaList> = { + component: "zeta-list", + title: "List", + args: { + divide: true + }, + parameters: { + design: { + url: "https://www.figma.com/file/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?type=design&node-id=19858-18383&mode=design&t=DUHsS5bjWB5UW1iG-4" + }, + status: { + type: "ready" + } + } +}; + +export default meta; + +export const List: StoryObj<ZetaList> = { + render: args => + html`<zeta-list .divide=${args.divide}> + <zeta-list-item headline="List Item"></zeta-list-item> + <zeta-list-item headline="List Item"></zeta-list-item> + <zeta-list-item headline="List Item"></zeta-list-item> + </zeta-list>` +}; diff --git a/src/stories/Navigation Bar/Docs.mdx b/src/stories/Navigation Bar/Docs.mdx new file mode 100644 index 0000000..f35fd5b --- /dev/null +++ b/src/stories/Navigation Bar/Docs.mdx @@ -0,0 +1,68 @@ +import { Canvas, Meta, Description, ArgTypes, Title, Stories } from "@storybook/blocks"; +import * as NavigationBar from "./navigation-bar.stories"; + +<Meta of={NavigationBar} title="Navigation Bar" /> + +<Title /> +<Description of="zeta-navigation-bar" /> +<Canvas of={NavigationBar.BarIconAndLabel} /> +[View Grid Menu Item](/docs/grid-menu-item--docs) + +<ArgTypes of="zeta-navigation-bar" /> +<Stories of="zeta-navigation-bar" includePrimary={false} /> + +## Dividers and Spacers + +## Divider Example + +To implement a divider in the navigation bar insert the below code between your nav-items: + +`<div class="divider"></div>` + +``` +<zeta-navigation-bar> + <zeta-grid-menu-item + icon="star" + label="Label" + .active=${selected === 0} + > + </zeta-grid-menu-item> + <div class="divider"></div> + <zeta-grid-menu-item + icon="star" + label="Label" + .active=${selected === 1} + > + </zeta-grid-menu-item> +</zeta-navigation-bar> +``` + +## Spacer Example + +To implement a spacer in the navigation bar insert the below code between your nav-items: + +`<div class="spacer"></div>` + +If you want the spacer to push all the other elements away from it pass the prop `shrinkItems` to `zeta-navigation-bar` like this: + +`<zeta-navigation-bar shrinkItems>` + +``` +<zeta-navigation-bar shrinkItems> + <zeta-grid-menu-item + icon="star" + label="Label" + .active=${selected === 0} + > + </zeta-grid-menu-item> + <div class="spacer"></div> + <zeta-grid-menu-item + icon="star" + label="Label" + .active=${selected === 1} + > + </zeta-grid-menu-item> +</zeta-navigation-bar> +``` + +--- diff --git a/src/stories/Navigation Bar/navigation-bar.stories.ts b/src/stories/Navigation Bar/navigation-bar.stories.ts new file mode 100644 index 0000000..be3962b --- /dev/null +++ b/src/stories/Navigation Bar/navigation-bar.stories.ts @@ -0,0 +1,177 @@ +import type { Meta, StoryObj } from "@storybook/web-components"; +import { html, nothing } from "lit"; +import { ZetaNavigationBar } from "../../components/navigation-bar/navigation-bar.js"; +import "../../components/grid-menu-item/grid-menu-item"; +import "../../components/button/button.js"; + +type _navBarType = ZetaNavigationBar & { numItems: number } & { divider: number } & { spacer: number } & { selected: number } & { button: Boolean } & { + notificationValue: string | boolean; +}; + +const staticArgTypes = { + divider: { table: { disable: true } }, + spacer: { table: { disable: true } }, + shrinkItems: { table: { disable: true } }, + numItems: { table: { disable: true } }, + selected: { table: { disable: true } }, + notificationValue: { table: { disable: true } }, + button: { table: { disable: true } }, + slot: { table: { disable: true } } +}; + +const meta: Meta<_navBarType> = { + component: "zeta-navigation-bar", + title: "Navigation Bar", + args: { + numItems: 4, + divider: 0, + spacer: 0, + shrinkItems: false, + button: false, + selected: 1 + }, + argTypes: { + numItems: { + control: { + type: "range", + min: 2, + max: 6, + step: 1 + } + }, + selected: { + control: { + type: "range", + min: 0, + max: 6, + step: 1 + } + }, + divider: { + control: { + type: "range", + min: 0, + max: 7, + step: 1 + } + }, + spacer: { + control: { + type: "range", + min: 0, + max: 7, + step: 1 + } + }, + notificationValue: { + options: [true, false, "1", "2", "3", "4", "5", "6", "7", "8", "9", "+"], + control: { + type: "select" + } + }, + slot: { table: { disable: true } } + }, + parameters: { + design: { + url: "https://www.figma.com/file/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?type=design&node-id=21186-40498&mode=design&t=DUHsS5bjWB5UW1iG-4" + }, + status: { + type: "ready" + } + } +}; + +export default meta; + +export const BarIconAndLabel: StoryObj<_navBarType> = { + render: args => { + const navigationBarItems = Array.from({ length: args.numItems }, (_, index) => { + return html` + ${index == (args.spacer as number) - 1 ? html` <div class="spacer"></div>` : nothing} + <zeta-grid-menu-item .active=${args.selected === index + 1} .notificationValue=${args.notificationValue}> + <zeta-icon slot="icon">star</zeta-icon> + Label + </zeta-grid-menu-item> + ${index == (args.divider as number) - 1 ? html` <div class="divider"></div>` : nothing} + ${index == args.numItems - 1 && index + 1 == (args.spacer as number) - 1 ? html` <div class="spacer"></div>` : nothing} + ${index == args.numItems - 1 && args.button == true ? html` <zeta-button>Button</zeta-button>` : nothing} + `; + }); + + return html`<zeta-navigation-bar ?shrinkItems=${args.shrinkItems}> ${navigationBarItems} </zeta-navigation-bar>`; + } +}; + +export const BarIconsOnly: StoryObj<_navBarType> = { + render: args => { + const navigationBarItems = Array.from({ length: args.numItems }, (_, index) => { + return html` + ${index == (args.spacer as number) - 1 ? html` <div class="spacer"></div>` : nothing} + <zeta-grid-menu-item .active=${args.selected === index + 1} .notificationValue=${args.notificationValue} + ><zeta-icon slot="icon">star</zeta-icon></zeta-grid-menu-item + > + + ${index == (args.divider as number) - 1 ? html` <div class="divider"></div>` : nothing} + ${index == args.numItems - 1 && index + 1 == (args.spacer as number) - 1 ? html` <div class="spacer"></div>` : nothing} + ${index == args.numItems - 1 && args.button == true ? html` <zeta-button>Button</zeta-button>` : nothing} + `; + }); + + return html`<zeta-navigation-bar ?shrinkItems=${args.shrinkItems}> ${navigationBarItems} </zeta-navigation-bar>`; + } +}; + +export const WithDivider: StoryObj<_navBarType> = { + argTypes: staticArgTypes, + + render: () => { + return html` <zeta-navigation-bar> + <zeta-grid-menu-item .active=${true}><zeta-icon slot="icon">star</zeta-icon>Label</zeta-grid-menu-item> + <zeta-grid-menu-item><zeta-icon slot="icon">star</zeta-icon>Label</zeta-grid-menu-item> + <div class="divider"></div> + <zeta-grid-menu-item><zeta-icon slot="icon">star</zeta-icon>Label</zeta-grid-menu-item> + <zeta-grid-menu-item><zeta-icon slot="icon">star</zeta-icon>Label</zeta-grid-menu-item> + </zeta-navigation-bar>`; + } +}; + +export const WithSpacer: StoryObj<_navBarType> = { + argTypes: staticArgTypes, + + render: () => { + return html` <zeta-navigation-bar> + <zeta-grid-menu-item .active=${true}><zeta-icon slot="icon">star</zeta-icon>Label</zeta-grid-menu-item> + <zeta-grid-menu-item><zeta-icon slot="icon">star</zeta-icon>Label</zeta-grid-menu-item> + <div class="spacer"></div> + <zeta-grid-menu-item><zeta-icon slot="icon">star</zeta-icon>Label</zeta-grid-menu-item> + <zeta-grid-menu-item><zeta-icon slot="icon">star</zeta-icon>Label</zeta-grid-menu-item> + </zeta-navigation-bar>`; + } +}; + +export const WithSpacerAndShrinkItems: StoryObj<_navBarType> = { + argTypes: staticArgTypes, + + render: () => { + return html` <zeta-navigation-bar shrinkItems> + <zeta-grid-menu-item .active=${true}><zeta-icon slot="icon">star</zeta-icon>Label</zeta-grid-menu-item> + <zeta-grid-menu-item><zeta-icon slot="icon">star</zeta-icon>Label</zeta-grid-menu-item> + <div class="spacer"></div> + <zeta-grid-menu-item><zeta-icon slot="icon">star</zeta-icon>Label</zeta-grid-menu-item> + <zeta-grid-menu-item><zeta-icon slot="icon">star</zeta-icon>Label</zeta-grid-menu-item> + </zeta-navigation-bar>`; + } +}; + +export const WithButton: StoryObj<_navBarType> = { + argTypes: staticArgTypes, + + render: () => { + return html` <zeta-navigation-bar> + <zeta-grid-menu-item .active=${true}><zeta-icon slot="icon">star</zeta-icon>Label</zeta-grid-menu-item> + <zeta-grid-menu-item><zeta-icon slot="icon">star</zeta-icon>Label</zeta-grid-menu-item> + <zeta-grid-menu-item><zeta-icon slot="icon">star</zeta-icon>Label</zeta-grid-menu-item> + <zeta-button>Button</zeta-button> + </zeta-navigation-bar>`; + } +}; diff --git a/src/stories/Navigation Drawer/Docs.mdx b/src/stories/Navigation Drawer/Docs.mdx new file mode 100644 index 0000000..12e258d --- /dev/null +++ b/src/stories/Navigation Drawer/Docs.mdx @@ -0,0 +1,37 @@ +import { Canvas, Meta, Description, ArgTypes, Title } from "@storybook/blocks"; +import * as NavigationDrawer from "./navigation-drawer.stories"; +import * as Header from "./navigation-drawer-header.stories"; +import * as Item from "./navigation-drawer-item.stories"; +import * as SubItem from "./navigation-drawer-sub-item.stories"; +import * as Footer from "./navigation-drawer-footer.stories"; + +<Meta of={NavigationDrawer} title="Navigation Drawer" /> + +<Title /> +<Description of="zeta-navigation-drawer" /> +<Canvas of={NavigationDrawer.NavigationDrawer} /> +<ArgTypes of="zeta-navigation-drawer" /> + +# Header + +<Description of="zeta-navigation-drawer-header" /> +<Canvas of={Header.Header} /> +<ArgTypes of="zeta-navigation-drawer-header" /> + +# Item + +<Description of="zeta-navigation-drawer-item" /> +<Canvas of={Item.Item} /> +<ArgTypes of="zeta-navigation-drawer-item" /> + +# Sub Item + +<Description of="zeta-navigation-drawer-sub-item" /> +<Canvas of={SubItem.SubItem} /> +<ArgTypes of="zeta-navigation-drawer-sub-item" /> + +# Footer + +<Description of="zeta-navigation-drawer-footer" /> +<Canvas of={Footer.Footer} /> +<ArgTypes of="zeta-navigation-drawer-footer" /> diff --git a/src/stories/Navigation Drawer/navigation-drawer-footer.stories.ts b/src/stories/Navigation Drawer/navigation-drawer-footer.stories.ts new file mode 100644 index 0000000..dacb35c --- /dev/null +++ b/src/stories/Navigation Drawer/navigation-drawer-footer.stories.ts @@ -0,0 +1,94 @@ +import type { Meta, StoryObj } from "@storybook/web-components"; +import { html } from "lit"; +import { styleMap } from "lit/directives/style-map.js"; +import { ZetaNavigationDrawerFooter } from "../../components/navigation-drawer/navigation-drawer-footer/navigation-drawer-footer.js"; +import { spreadGenerator } from "../utils.js"; +const spread = spreadGenerator(ZetaNavigationDrawerFooter); +import "../../components/avatar/avatar.js"; +import "../../components/icon/icon.js"; + +const meta: Meta<ZetaNavigationDrawerFooter> = { + title: "Navigation Drawer", + component: "zeta-navigation-drawer-footer", + args: { + headline: "Title", + subHeadline: "subtitle", + divide: false + }, + argTypes: { + variant: { table: { disable: true } }, + hideDefaultLogo: { table: { disable: true } } + } +}; +export default meta; + +export const Footer: StoryObj = { + parameters: { + design: { + url: "https://www.figma.com/file/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?type=design&node-id=1092-21721&mode=design&t=DUHsS5bjWB5UW1iG-4" + }, + status: { + type: "needsAttention" + } + }, + render: args => html` <zeta-navigation-drawer-footer ${spread(args)}> </zeta-navigation-drawer-footer> ` +}; + +export const FooterAvatarAndIcon: StoryObj = { + parameters: { + design: { + url: "https://www.figma.com/file/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?type=design&node-id=1092-21721&mode=design&t=DUHsS5bjWB5UW1iG-4" + }, + status: { + type: "needsAttention" + } + }, + + render: args => + html`<zeta-navigation-drawer-footer ${spread(args)}> + <zeta-avatar slot="leading"></zeta-avatar> + <zeta-icon slot="trailing" color="white">settings</zeta-icon> + </zeta-navigation-drawer-footer>` +}; + +export const FooterDefaultLogo: StoryObj = { + args: { + variant: "logo" + }, + argTypes: { + subHeadline: { table: { disable: true } } + }, + parameters: { + design: { + url: "https://www.figma.com/file/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?type=design&node-id=1113-32425&mode=design&t=DUHsS5bjWB5UW1iG-4" + }, + status: { + type: "needsAttention" + } + }, + render: args => html`<zeta-navigation-drawer-footer ${spread(args)}></zeta-navigation-drawer-footer>` +}; + +export const FooterCustomLogo: StoryObj = { + args: { + variant: "logo" + }, + argTypes: { + subHeadline: { table: { disable: true } } + }, + parameters: { + design: { + url: "https://www.figma.com/file/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?type=design&node-id=1113-32425&mode=design&t=DUHsS5bjWB5UW1iG-4" + }, + status: { + type: "needsAttention" + } + }, + render: args => html`<zeta-navigation-drawer-footer ${spread(args)}> + <img style=${styleMap({ + width: "200px", + height: "var(--spacing-20)", + objectFit: "contain" + })} slot="logo" src="https://upload.wikimedia.org/wikipedia/commons/thumb/2/2f/Google_2015_logo.svg/1200px-Google_2015_logo.svg.png"></img> + </zeta-navigation-drawer-footer>` +}; diff --git a/src/stories/Navigation Drawer/navigation-drawer-header.stories.ts b/src/stories/Navigation Drawer/navigation-drawer-header.stories.ts new file mode 100644 index 0000000..54556ca --- /dev/null +++ b/src/stories/Navigation Drawer/navigation-drawer-header.stories.ts @@ -0,0 +1,38 @@ +import type { Meta, StoryObj } from "@storybook/web-components"; +import { html } from "lit"; +import { ZetaNavigationDrawerHeader } from "../../components/navigation-drawer/navigation-drawer-header/navigation-drawer-header.js"; +import { spreadGenerator } from "../utils.js"; +const spread = spreadGenerator(ZetaNavigationDrawerHeader); +import "../../components/avatar/avatar.js"; +import "../../components/icon/icon.js"; + +const meta: Meta<ZetaNavigationDrawerHeader> = { + component: "zeta-navigation-drawer-header", + title: "Navigation Drawer", + args: { + headline: "Title", + subHeadline: "subtitle", + divide: false + }, + parameters: { + design: { + url: "https://www.figma.com/file/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?type=design&node-id=1092-21721&mode=design&t=DUHsS5bjWB5UW1iG-4" + }, + status: { + type: "needsAttention" + } + } +}; +export default meta; + +export const Header: StoryObj = { + render: args => html`<zeta-navigation-drawer-header ${spread(args)}></zeta-navigation-drawer-header>` +}; + +export const HeaderAvatarAndIcon: StoryObj = { + render: args => + html`<zeta-navigation-drawer-header ${spread(args)}> + <zeta-avatar slot="leading"></zeta-avatar> + <zeta-icon slot="trailing" color="white">settings</zeta-icon> + </zeta-navigation-drawer-header>` +}; diff --git a/src/stories/Navigation Drawer/navigation-drawer-item.stories.ts b/src/stories/Navigation Drawer/navigation-drawer-item.stories.ts new file mode 100644 index 0000000..c7acec4 --- /dev/null +++ b/src/stories/Navigation Drawer/navigation-drawer-item.stories.ts @@ -0,0 +1,94 @@ +import type { Meta, StoryObj } from "@storybook/web-components"; +import { html } from "lit"; +import { ZetaNavigationDrawerItem } from "../../components/navigation-drawer/navigation-drawer-item/navigation-drawer-item.js"; +import { ifDefined } from "lit/directives/if-defined.js"; +import "../../components/icon/icon.js"; +import "../../components/badges/label/label.js"; +import { ZetaIconNameList } from "@zebra-fed/zeta-icons"; + +const meta: Meta<ZetaNavigationDrawerItem> = { + component: "zeta-navigation-drawer-item", + title: "Navigation Drawer", + args: { + headline: "Navigation Item", + rounded: true, + disabled: false, + active: false + }, + parameters: { + design: { + url: "https://www.figma.com/file/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?type=design&node-id=1092-22034&mode=design&t=DUHsS5bjWB5UW1iG-4" + }, + status: { + type: "needsAttention" + } + } +}; +export default meta; + +export const Item: StoryObj<ZetaNavigationDrawerItem> = { + argTypes: { + rounded: { table: { disable: true } } + } +}; + +export const ItemWithLeadingIcon: StoryObj<ZetaNavigationDrawerItem | any> = { + args: { + leading: "star" + }, + argTypes: { + leading: { + options: ZetaIconNameList, + control: { type: "select" } + } + }, + render: args => + html`<zeta-navigation-drawer-item headline=${ifDefined(args.headline)} .rounded=${args.rounded} .disabled=${args.disabled} .active=${args.active}> + <zeta-icon slot="leading">${args.leading}</zeta-icon> + </zeta-navigation-drawer-item>` +}; + +export const ItemWithLeadingAndTrailingIcon: StoryObj<ZetaNavigationDrawerItem | any> = { + args: { + leading: "star", + trailing: "more_vertical" + }, + argTypes: { + leading: { + options: ZetaIconNameList, + control: { type: "select" } + }, + trailing: { + options: ZetaIconNameList, + control: { type: "select" } + } + }, + render: args => + html`<zeta-navigation-drawer-item headline=${ifDefined(args.headline)} .rounded=${args.rounded} .disabled=${args.disabled} .active=${args.active}> + <zeta-icon slot="leading">${args.leading}</zeta-icon> + <zeta-icon slot="trailing">${args.trailing}</zeta-icon> + </zeta-navigation-drawer-item>` +}; + +export const ItemWithBadge: StoryObj<ZetaNavigationDrawerItem | any> = { + args: { + leading: "star", + trailing: "more_vertical" + }, + argTypes: { + leading: { + options: ZetaIconNameList, + control: { type: "select" } + }, + trailing: { + options: ZetaIconNameList, + control: { type: "select" } + } + }, + render: args => + html`<zeta-navigation-drawer-item headline=${ifDefined(args.headline)} .rounded=${args.rounded} .disabled=${args.disabled} .active=${args.active}> + <zeta-icon slot="leading">${args.leading}</zeta-icon> + <zeta-label slot="badge" status="info">99+</zeta-label> + <zeta-icon slot="trailing">${args.trailing}</zeta-icon> + </zeta-navigation-drawer-item>` +}; diff --git a/src/stories/Navigation Drawer/navigation-drawer-sub-item.stories.ts b/src/stories/Navigation Drawer/navigation-drawer-sub-item.stories.ts new file mode 100644 index 0000000..eee3978 --- /dev/null +++ b/src/stories/Navigation Drawer/navigation-drawer-sub-item.stories.ts @@ -0,0 +1,24 @@ +import type { Meta, StoryObj } from "@storybook/web-components"; +import { ZetaNavigationDrawerSubItem } from "../../components/navigation-drawer/navigation-drawer-sub-item/navigation-drawer-sub-item.js"; + +const meta: Meta<ZetaNavigationDrawerSubItem> = { + component: "zeta-navigation-drawer-sub-item", + title: "Navigation Drawer", + args: { + headline: "Navigation Sub Item", + rounded: true, + active: false, + disabled: false + }, + parameters: { + design: { + url: "https://www.figma.com/file/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?type=design&node-id=1092-24772&mode=design&t=DUHsS5bjWB5UW1iG-4" + }, + status: { + type: "needsAttention" + } + } +}; +export default meta; + +export const SubItem: StoryObj<ZetaNavigationDrawerSubItem> = {}; diff --git a/src/stories/Navigation Drawer/navigation-drawer.stories.ts b/src/stories/Navigation Drawer/navigation-drawer.stories.ts new file mode 100644 index 0000000..9957081 --- /dev/null +++ b/src/stories/Navigation Drawer/navigation-drawer.stories.ts @@ -0,0 +1,98 @@ +import type { Meta, StoryObj } from "@storybook/web-components"; +import { html } from "lit"; +import { ZetaNavigationDrawer } from "../../components/navigation-drawer/navigation-drawer.js"; +import "../../components/navigation-drawer/navigation-drawer-sub-item/navigation-drawer-sub-item.js"; +import "../../components/navigation-drawer/navigation-drawer-item/navigation-drawer-item.js"; +import "../../components/navigation-drawer/navigation-drawer-footer/navigation-drawer-footer.js"; +import "../../components/navigation-drawer/navigation-drawer-header/navigation-drawer-header.js"; +import "../../components/avatar/avatar.js"; +import "../../components/icon/icon.js"; +import "../../components/button/button.js"; + +const meta: Meta<ZetaNavigationDrawer> = { + component: "zeta-navigation-drawer", + title: "Navigation Drawer", + args: { + anchor: "left", + showAnimation: true + }, + argTypes: { + anchor: { + options: ["right", "left"], + control: { + type: "select" + } + }, + open: { table: { disable: true } }, + initialOpen: { table: { disable: true } } + }, + parameters: { + design: { + url: "https://www.figma.com/file/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?type=design&node-id=1788-53238&mode=design&t=DUHsS5bjWB5UW1iG-4" + }, + status: { + type: "designPending" + } + } +}; + +export default meta; + +export const NavigationDrawer: StoryObj<ZetaNavigationDrawer> = { + render: args => + html` <div style="min-height: 600px"> + <zeta-navigation-drawer id="drawer" ?showAnimation=${args.showAnimation} anchor=${args.anchor} .initialOpen=${true}> + <zeta-navigation-drawer-header slot="header" headline="Title" subHeadline="Subtitle"> + <zeta-avatar slot="leading"></zeta-avatar> + <zeta-icon slot="trailing" color="white">settings</zeta-icon> + </zeta-navigation-drawer-header> + <zeta-navigation-drawer-item active><zeta-icon slot="leading">star</zeta-icon> Navigation Item</zeta-navigation-drawer-item> + <zeta-navigation-drawer-item><zeta-icon slot="leading">star</zeta-icon> Navigation Item</zeta-navigation-drawer-item> + <zeta-navigation-drawer-sub-item>Navigation Sub Item</zeta-navigation-drawer-sub-item> + <zeta-navigation-drawer-item><zeta-icon slot="leading">star</zeta-icon> Navigation Item</zeta-navigation-drawer-item> + <zeta-navigation-drawer-item><zeta-icon slot="leading">star</zeta-icon> Navigation Item</zeta-navigation-drawer-item> + <zeta-navigation-drawer-item><zeta-icon slot="leading">star</zeta-icon> Navigation Item</zeta-navigation-drawer-item> + <zeta-navigation-drawer-item><zeta-icon slot="leading">star</zeta-icon> Navigation Item</zeta-navigation-drawer-item> + <zeta-navigation-drawer-item><zeta-icon slot="leading">star</zeta-icon> Navigation Item</zeta-navigation-drawer-item> + <zeta-navigation-drawer-item><zeta-icon slot="leading">star</zeta-icon> Navigation Item</zeta-navigation-drawer-item> + <zeta-navigation-drawer-item><zeta-icon slot="leading">star</zeta-icon> Navigation Item</zeta-navigation-drawer-item> + <zeta-navigation-drawer-item><zeta-icon slot="leading">star</zeta-icon> Navigation Item</zeta-navigation-drawer-item> + <zeta-navigation-drawer-item><zeta-icon slot="leading">star</zeta-icon> Navigation Item</zeta-navigation-drawer-item> + <zeta-navigation-drawer-item><zeta-icon slot="leading">star</zeta-icon> Navigation Item</zeta-navigation-drawer-item> + <zeta-navigation-drawer-footer slot="footer" variant="logo">version 1.0.1</zeta-navigation-drawer-footer> + </zeta-navigation-drawer> + </div>` +}; +export const ShowNavigationDrawer: StoryObj<ZetaNavigationDrawer> = { + render: args => + html`<div> + <zeta-button + @click=${() => { + const drawer = document.querySelector("#drawer") as ZetaNavigationDrawer; + void drawer.show(); + }} + > + Open drawer + </zeta-button> + <zeta-navigation-drawer id="drawer" ?showAnimation=${args.showAnimation} anchor=${args.anchor}> + <zeta-navigation-drawer-header slot="header" headline="Title" subHeadline="Subtitle"> + <zeta-avatar slot="leading"></zeta-avatar> + <zeta-icon slot="trailing" color="white">settings</zeta-icon> + </zeta-navigation-drawer-header> + <zeta-navigation-drawer-item active><zeta-icon slot="leading">star</zeta-icon> Navigation Item</zeta-navigation-drawer-item> + <zeta-navigation-drawer-item><zeta-icon slot="leading">star</zeta-icon> Navigation Item</zeta-navigation-drawer-item> + <zeta-navigation-drawer-sub-item>Navigation Sub Item</zeta-navigation-drawer-sub-item> + <zeta-navigation-drawer-item><zeta-icon slot="leading">star</zeta-icon> Navigation Item</zeta-navigation-drawer-item> + <zeta-navigation-drawer-item><zeta-icon slot="leading">star</zeta-icon> Navigation Item</zeta-navigation-drawer-item> + <zeta-navigation-drawer-item><zeta-icon slot="leading">star</zeta-icon> Navigation Item</zeta-navigation-drawer-item> + <zeta-navigation-drawer-item><zeta-icon slot="leading">star</zeta-icon> Navigation Item</zeta-navigation-drawer-item> + <zeta-navigation-drawer-item><zeta-icon slot="leading">star</zeta-icon> Navigation Item</zeta-navigation-drawer-item> + <zeta-navigation-drawer-item><zeta-icon slot="leading">star</zeta-icon> Navigation Item</zeta-navigation-drawer-item> + <zeta-navigation-drawer-item><zeta-icon slot="leading">star</zeta-icon> Navigation Item</zeta-navigation-drawer-item> + <zeta-navigation-drawer-item><zeta-icon slot="leading">star</zeta-icon> Navigation Item</zeta-navigation-drawer-item> + <zeta-navigation-drawer-item><zeta-icon slot="leading">star</zeta-icon> Navigation Item</zeta-navigation-drawer-item> + <zeta-navigation-drawer-item><zeta-icon slot="leading">star</zeta-icon> Navigation Item</zeta-navigation-drawer-item> + <zeta-navigation-drawer-footer slot="footer" variant="logo">version 1.0.1</zeta-navigation-drawer-footer> + </zeta-navigation-drawer> + </div>` +}; diff --git a/src/stories/Navigation Rail/Docs.mdx b/src/stories/Navigation Rail/Docs.mdx new file mode 100644 index 0000000..4fbe30a --- /dev/null +++ b/src/stories/Navigation Rail/Docs.mdx @@ -0,0 +1,30 @@ +import { Canvas, Meta, Description, ArgTypes, Title } from "@storybook/blocks"; + +import * as NavigationRail from "./navigation-rail.stories"; +import * as NavigationRailItem from "./navigation-rail-item.stories"; + +<Meta of={NavigationRail} title="Navigation Rail" /> + +<Title /> + +<Canvas of={NavigationRail.NavigationRail} /> + +<Description of={NavigationRail} /> + +<ArgTypes of={NavigationRail} /> + +## Navigation Rail with Navigation + +To add navigation to a navigation rail, add provide urls to the `href` attribute of each `zeta-navigation-rail-item`. + +You can then conditionally set the `selected` attribute on the current pages navigation item based on the current url. + +To see this working, [see the example here](./?path=/story/navigation-rail--navigation-rail-with-navigation). + +# Navigation Rail item + +<Canvas of={NavigationRailItem.NavigationRailItem} /> + +<Description of={NavigationRailItem} /> + +<ArgTypes of={NavigationRailItem} /> diff --git a/src/stories/Navigation Rail/navigation-rail-item.stories.ts b/src/stories/Navigation Rail/navigation-rail-item.stories.ts new file mode 100644 index 0000000..da4ffcd --- /dev/null +++ b/src/stories/Navigation Rail/navigation-rail-item.stories.ts @@ -0,0 +1,39 @@ +import type { Meta, StoryObj } from "@storybook/web-components"; +import type { ZetaNavigationRailItem as ZetaNavigationRailItem } from "../../components/navigation-rail/navigation-rail-item"; +import { ZetaIconNameList, type ZetaIconName } from "@zebra-fed/zeta-icons"; +import "../../components/navigation-rail/navigation-rail-item.js"; +import { html } from "lit"; +import "../../components/icon/icon"; + +type ZetaNavigationRailItemStory = ZetaNavigationRailItem & { icon: ZetaIconName }; + +const meta: Meta<ZetaNavigationRailItemStory> = { + component: "zeta-navigation-rail-item", + title: "Navigation Rail", + args: { + slot: "Label", + icon: "star", + disabled: false, + rounded: true, + selected: false, + href: undefined + }, + argTypes: { + icon: { + control: { + type: "select" + }, + options: ZetaIconNameList + } + } +}; + +export default meta; + +export const NavigationRailItem: StoryObj<ZetaNavigationRailItemStory> = { + render: ({ slot, icon, ...args }) => { + return html`<zeta-navigation-rail-item .disabled=${args.disabled} .selected=${args.selected} .rounded=${args.rounded} .href=${args.href} + ><zeta-icon slot="icon">${icon}</zeta-icon>${slot}</zeta-navigation-rail-item + >`; + } +}; diff --git a/src/stories/Navigation Rail/navigation-rail.stories.ts b/src/stories/Navigation Rail/navigation-rail.stories.ts new file mode 100644 index 0000000..ad2bb86 --- /dev/null +++ b/src/stories/Navigation Rail/navigation-rail.stories.ts @@ -0,0 +1,56 @@ +import type { Meta, StoryObj } from "@storybook/web-components"; +import type { ZetaNavigationRail } from "../../components/navigation-rail/navigation-rail"; +import { html } from "lit"; +import "../../components/navigation-rail/navigation-rail"; +import "../../components/navigation-rail/navigation-rail-item"; +import "../../components/icon/icon"; + +const meta: Meta<ZetaNavigationRail> = { + title: "Navigation Rail", + component: "zeta-navigation-rail", + args: { + rounded: true + }, + parameters: { + design: { + url: "https://www.figma.com/design/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?node-id=229-43" + }, + status: { + type: "ready" + } + } +}; + +export default meta; + +export const NavigationRail: StoryObj<ZetaNavigationRail> = { + argTypes: { + slot: { table: { disable: true } } + }, + render: args => html` + <zeta-navigation-rail .rounded=${args.rounded}> + <zeta-navigation-rail-item selected tabIndex="1"><zeta-icon slot="icon">star</zeta-icon>Item</zeta-navigation-rail-item> + <zeta-navigation-rail-item tabIndex="1"><zeta-icon slot="icon">star</zeta-icon>Item</zeta-navigation-rail-item> + <zeta-navigation-rail-item tabIndex="1"><zeta-icon slot="icon">star</zeta-icon>Item</zeta-navigation-rail-item> + </zeta-navigation-rail> + ` +}; + +export const NavigationRailWithNavigation: StoryObj<ZetaNavigationRail> = { + argTypes: { + slot: { table: { disable: true } } + }, + render: args => { + const items = []; + const baseUrl = `${document.location.origin}?path=/story/navigation-rail--navigation-rail-with-navigation`; + const searchParams = new URLSearchParams(document.location.search); + for (let i = 0; i < 3; i++) { + items.push( + html`<zeta-navigation-rail-item href="${baseUrl}&index=${i}" tabIndex="1" ?selected=${i.toString() === searchParams.get("index")} + ><zeta-icon slot="icon">star</zeta-icon>Item</zeta-navigation-rail-item + >` + ); + } + return html` <zeta-navigation-rail .rounded=${args.rounded}> ${items} </zeta-navigation-rail> `; + } +}; diff --git a/src/stories/Progress/Docs.mdx b/src/stories/Progress/Docs.mdx new file mode 100644 index 0000000..3686d9c --- /dev/null +++ b/src/stories/Progress/Docs.mdx @@ -0,0 +1,19 @@ +import { Canvas, Meta, Description, ArgTypes, Title } from "@storybook/blocks"; +import * as Circle from "./progress-circle.stories"; +import * as Bar from "./progress-bar.stories"; + +<Meta of={Circle} title="Progress" /> + +<Title /> + +# Progress Circle + +<Description of="zeta-progress-circle" /> +<Canvas of={Circle.ProgressCircle} /> +<ArgTypes of="zeta-progress-circle" /> + +# Progress Bar + +<Description of="zeta-progress-bar" /> +<Canvas of={Bar.ProgressBar} /> +<ArgTypes of="zeta-progress-bar" /> diff --git a/src/stories/Progress/progress-bar.stories.ts b/src/stories/Progress/progress-bar.stories.ts new file mode 100644 index 0000000..559eba0 --- /dev/null +++ b/src/stories/Progress/progress-bar.stories.ts @@ -0,0 +1,35 @@ +import type { Meta, StoryObj } from "@storybook/web-components"; +import { ZetaProgressBar } from "../../components/progress-indicators/progress-bar/progress-bar.js"; + +const meta: Meta<ZetaProgressBar> = { + component: "zeta-progress-bar", + title: "Progress", + args: { + rounded: true, + size: "medium", + value: 50, + label: "Loading...", + indeterminate: false, + buffering: false + }, + argTypes: { + size: { + options: ["thin", "medium"], + control: { + type: "select" + } + } + }, + parameters: { + design: { + url: "https://www.figma.com/file/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?type=design&node-id=1358-31337&mode=design&t=DUHsS5bjWB5UW1iG-4" + }, + status: { + type: "ready" + } + } +}; + +export default meta; + +export const ProgressBar: StoryObj<ZetaProgressBar> = {}; diff --git a/src/stories/Progress/progress-circle.stories.ts b/src/stories/Progress/progress-circle.stories.ts new file mode 100644 index 0000000..fb32eb1 --- /dev/null +++ b/src/stories/Progress/progress-circle.stories.ts @@ -0,0 +1,39 @@ +import type { Meta, StoryObj } from "@storybook/web-components"; +import { ZetaProgressCircle } from "../../components/progress-indicators/progress-circle/progress-circle.js"; + +const meta: Meta<ZetaProgressCircle> = { + component: "zeta-progress-circle", + title: "Progress", + args: { + progress: 75, + size: 64, + rounded: false, + type: "default" + }, + argTypes: { + size: { + options: [24, 36, 40, 48, 64], + control: { + type: "inline-radio" + } + }, + type: { + options: ["default", "upload"], + control: { + type: "select" + } + } + }, + parameters: { + design: { + url: "https://www.figma.com/file/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?type=design&node-id=900-10416&mode=design&t=DUHsS5bjWB5UW1iG-4" + }, + status: { + type: "ready" + } + } +}; + +export const ProgressCircle: StoryObj<ZetaProgressCircle> = {}; + +export default meta; diff --git a/src/stories/Segmented Control/Docs.mdx b/src/stories/Segmented Control/Docs.mdx new file mode 100644 index 0000000..2be4206 --- /dev/null +++ b/src/stories/Segmented Control/Docs.mdx @@ -0,0 +1,30 @@ +import { Canvas, Meta, Description, ArgTypes, Title } from "@storybook/blocks"; +import * as ZetaSegmentedControl from "./segmented-control.stories.ts"; +import * as ZetaSegmentedItem from "./segmented-item.stories.ts"; + +<Meta of={ZetaSegmentedControl} title="Segmented Control" /> + +<Title /> +<Canvas of={ZetaSegmentedControl.SegmentedControl} /> + +<Description of="zeta-segmented-control" /> + +<ArgTypes of={ZetaSegmentedControl} /> + +## Segmented Control with Icons + +<Canvas of={ZetaSegmentedControl.SegmentedControlWithIcons} /> + +# Segmented Item + +<Description of="zeta-segmented-item" /> + +<ArgTypes of={ZetaSegmentedItem} /> + +## Segmented Item with text + +<Canvas of={ZetaSegmentedItem.SegmentedItemText} /> + +## Segmented Item with an icon + +<Canvas of={ZetaSegmentedItem.SegmentedItemIcon} /> diff --git a/src/stories/Segmented Control/segmented-control.stories.ts b/src/stories/Segmented Control/segmented-control.stories.ts new file mode 100644 index 0000000..d34d343 --- /dev/null +++ b/src/stories/Segmented Control/segmented-control.stories.ts @@ -0,0 +1,47 @@ +import type { Meta, StoryObj } from "@storybook/web-components"; +import type { ZetaSegmentedControl } from "../../components/segmented-control/segmented-control"; +import { html } from "lit"; +import "../../components/segmented-control/segmented-control"; +import "../../components/segmented-control/segmented-item"; +import "../../components/icon/icon"; + +const meta: Meta<ZetaSegmentedControl> = { + title: "Segmented Control", + component: "zeta-segmented-control", + args: { + rounded: true + }, + parameters: { + design: { + url: "https://www.figma.com/design/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?node-id=1046-20148" + }, + status: { + type: "ready" + } + } +}; + +export default meta; + +export const SegmentedControl: StoryObj<ZetaSegmentedControl> = { + argTypes: { slot: { table: { disable: true } } }, + render: args => html` + <zeta-segmented-control .rounded=${args.rounded}> + <zeta-segmented-item>Item 1</zeta-segmented-item> + <zeta-segmented-item>Item 2</zeta-segmented-item> + <zeta-segmented-item active>Item 3</zeta-segmented-item> + <zeta-segmented-item>Item 4</zeta-segmented-item> + </zeta-segmented-control> + ` +}; + +export const SegmentedControlWithIcons: StoryObj<ZetaSegmentedControl> = { + argTypes: { slot: { table: { disable: true } } }, + render: args => html` + <zeta-segmented-control .rounded=${args.rounded}> + <zeta-segmented-item active><zeta-icon>star</zeta-icon></zeta-segmented-item> + <zeta-segmented-item><zeta-icon>star</zeta-icon></zeta-segmented-item> + <zeta-segmented-item><zeta-icon>star</zeta-icon></zeta-segmented-item> + </zeta-segmented-control> + ` +}; diff --git a/src/stories/Segmented Control/segmented-item.stories.ts b/src/stories/Segmented Control/segmented-item.stories.ts new file mode 100644 index 0000000..7ef9cd9 --- /dev/null +++ b/src/stories/Segmented Control/segmented-item.stories.ts @@ -0,0 +1,43 @@ +import type { Meta, StoryObj } from "@storybook/web-components"; +import { ZetaSegmentedItem } from "../../components/segmented-control/segmented-item.js"; +import "../../components/icon/icon.js"; +import { html } from "lit"; +import { ZetaIconNameList } from "@zebra-fed/zeta-icons"; + +const meta: Meta<ZetaSegmentedItem> = { + component: "zeta-segmented-item", + title: "Segmented Control", + args: { + rounded: true, + active: true + } +}; + +export default meta; + +export const SegmentedItemText: StoryObj<ZetaSegmentedItem> = { + args: { + slot: "Item 1" + }, + render: props => { + return html`<zeta-segmented-item .active=${props.active} .rounded=${props.rounded}>${props.slot}</zeta-segmented-item>`; + } +}; + +export const SegmentedItemIcon: StoryObj<ZetaSegmentedItem> = { + args: { + slot: "star" + }, + argTypes: { + slot: { + name: "Icon", + options: ZetaIconNameList, + control: { + type: "select" + } + } + }, + render: props => { + return html`<zeta-segmented-item .active=${props.active} .rounded=${props.rounded}><zeta-icon>${props.slot}</zeta-icon></zeta-segmented-item>`; + } +}; diff --git a/src/stories/Slider/Docs.mdx b/src/stories/Slider/Docs.mdx new file mode 100644 index 0000000..34019c9 --- /dev/null +++ b/src/stories/Slider/Docs.mdx @@ -0,0 +1,23 @@ +import { Canvas, Meta, Description, ArgTypes, Title } from "@storybook/blocks"; +import * as Slider from "./slider.stories"; +import * as SliderInputField from "./slider-input-field.stories"; +import * as RangeSelector from "./range-selector.stories"; + +<Meta of={Slider} title="Sliders" /> + +<Title /> +<Description of="zeta-slider" /> +<Canvas of={Slider.Slider} /> +<ArgTypes of="zeta-slider" /> + +# Slider Input Field + +<Description of="zeta-slider-input-field" /> +<Canvas of={SliderInputField.SliderInputField} /> +<ArgTypes of="zeta-slider-input-field" /> + +# Range Selector + +<Description of="zeta-slider" /> +<Canvas of={RangeSelector.RangeSlider} /> +<ArgTypes of="zeta-slider" /> diff --git a/src/stories/Slider/range-selector.stories.ts b/src/stories/Slider/range-selector.stories.ts new file mode 100644 index 0000000..ab32065 --- /dev/null +++ b/src/stories/Slider/range-selector.stories.ts @@ -0,0 +1,37 @@ +import type { Meta, StoryObj } from "@storybook/web-components"; +import { ZetaRangeSelector } from "../../components/slider/range-selector/range-selector.js"; +import { html } from "lit"; +import { spreadGenerator } from "../utils.js"; +const spread = spreadGenerator(ZetaRangeSelector); + +const meta: Meta<ZetaRangeSelector> = { + component: "zeta-range-selector", + title: "Slider", + args: { + rounded: true, + disabled: false, + label: "Label", + min: 0, + max: 100, + stepIncrement: 0 + }, + argTypes: { + stepIncrement: { control: { type: "range", min: 0, max: 50 } }, + min: { control: { type: "number", min: 0, max: 100 } }, + max: { control: { type: "number", min: 0, max: 100 } } + }, + parameters: { + design: { + url: "https://www.figma.com/design/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?node-id=980-16448&m=dev" + }, + status: { + type: "needsAttention" + } + } +}; + +export default meta; + +export const RangeSlider: StoryObj = { + render: args => html`<zeta-range-selector ${spread(args)} .initialValues=${{ min: 10, max: 90 }}> </zeta-range-selector>` +}; diff --git a/src/stories/Slider/slider-input-field.stories.ts b/src/stories/Slider/slider-input-field.stories.ts new file mode 100644 index 0000000..1e373de --- /dev/null +++ b/src/stories/Slider/slider-input-field.stories.ts @@ -0,0 +1,40 @@ +import type { Meta, StoryObj } from "@storybook/web-components"; +import { ZetaSliderInputField } from "../../components/slider/slider-input-field/slider-input-field.js"; +import { spreadGenerator } from "../utils.js"; +const spread = spreadGenerator(ZetaSliderInputField); +import { html } from "lit"; + +const meta: Meta<ZetaSliderInputField> = { + component: "zeta-slider-input-field", + title: "Slider", + args: { + rounded: true, + disabled: false, + error: false, + label: "Label", + initialValue: 50, + min: 0, + max: 100, + stepIncrement: 0 + }, + argTypes: { + name: { table: { disable: true } }, + value: { table: { disable: true } }, + stepIncrement: { control: { type: "range", min: 0, max: 50 } }, + min: { control: { type: "number", min: 0, max: 100 } }, + max: { control: { type: "number", min: 0, max: 100 } } + }, + parameters: { + design: { + url: "https://www.figma.com/design/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?node-id=875-11860&m=dev" + }, + status: { + type: "needsAttention" + } + } +}; +export default meta; + +export const SliderInputField: StoryObj = { + render: args => html` <zeta-slider-input-field ${spread(args)}> </zeta-slider-input-field>` +}; diff --git a/src/stories/Slider/slider.stories.ts b/src/stories/Slider/slider.stories.ts new file mode 100644 index 0000000..e64612d --- /dev/null +++ b/src/stories/Slider/slider.stories.ts @@ -0,0 +1,45 @@ +import type { Meta, StoryObj } from "@storybook/web-components"; +import { ZetaSlider } from "../../components/slider/slider.js"; +import { html } from "lit"; +import { spreadGenerator } from "../utils.js"; +import type { ZetaRangeSliderEvent, ZetaRangeSliderEventDetail, ZetaSliderEvent, ZetaSliderEventDetail } from "../../events.js"; +const spread = spreadGenerator(ZetaSlider); + +const meta: Meta<ZetaSlider> = { + component: "zeta-slider", + title: "Slider", + args: { rounded: true, disabled: false, value: 50, lowerValue: 10, upperValue: 90, min: 0, max: 100, stepIncrement: 0, type: "default" }, + argTypes: { + type: { control: { type: "inline-radio" }, options: ["range", "default"] }, + value: { table: { disable: true } }, + stepIncrement: { control: { type: "range", min: 0, max: 50 } }, + lowerValue: { table: { disable: true } }, + upperValue: { table: { disable: true } }, + min: { control: { type: "number", min: 0, max: 100 } }, + max: { control: { type: "number", min: 0, max: 100 } } + }, + parameters: { + design: { + url: "https://www.figma.com/design/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?node-id=875-11860&m=dev" + }, + status: { + type: "ready" + } + } +}; + +export default meta; + +export const Slider: StoryObj = { + render: args => + html`<zeta-slider + @zeta-slider-change=${(e: ZetaSliderEvent<ZetaSliderEventDetail>) => { + console.log(e.detail.value); + }} + @zeta-range-slider-change=${(e: ZetaRangeSliderEvent<ZetaRangeSliderEventDetail>) => { + console.log(e.detail.min, e.detail.max); + }} + ${spread(args)} + > + </zeta-slider>` +}; diff --git a/src/stories/Snackbar/Docs.mdx b/src/stories/Snackbar/Docs.mdx new file mode 100644 index 0000000..14c6275 --- /dev/null +++ b/src/stories/Snackbar/Docs.mdx @@ -0,0 +1,37 @@ +import { Canvas, Meta, Description, ArgTypes, Title, Story } from "@storybook/blocks"; +import * as Snackbar from "./snackbar.stories"; + +<Meta of={Snackbar} title="Snackbar" /> + +<Title /> +<Description of="zeta-snackbar" /> + +<div + style={{ + margin: "25px 30px 0", + padding: "40px 30px", + borderRadius: "4px", + background: "#FFFFFF", + boxShadow: "rgba(0, 0, 0, 0.10) 0 1px 3px 0", + border: "1px solid hsla(203, 50%, 30%, 0.15)" + }}> + <Story of={Snackbar.Snackbar} /> + +</div> + +<div + style={{ + margin: "0 15px" + }} +> + ```jsx + <zeta-snackbar status="default" round="full" hasCloseAction actionLabel="Action" actionClick={() => console.log("Action Clicked")}> + <zeta-icon slot="icon">happy</zeta-icon> + Message + </zeta-snackbar> + ``` +</div> + +The action button will only be shown if the `actionLabel` and `actionClick` props are provided. + +<ArgTypes of="zeta-snackbar" /> diff --git a/src/stories/Snackbar/snackbar.stories.ts b/src/stories/Snackbar/snackbar.stories.ts new file mode 100644 index 0000000..25417f8 --- /dev/null +++ b/src/stories/Snackbar/snackbar.stories.ts @@ -0,0 +1,97 @@ +import type { Meta, StoryObj } from "@storybook/web-components"; +import { html } from "lit"; +import "../../components/icon/icon.js"; +import type { ZetaSnackbar } from "../../components/snackbar/snackbar.js"; +import "../../components/snackbar/snackbar.js"; +import "../../components/button/icon-button/icon-button"; +import { ZetaIconNameList } from "@zebra-fed/zeta-icons"; + +const meta: Meta<ZetaSnackbar | any> = { + component: "zeta-Snackbar", + title: "Snackbar", + args: { + slotIcon: "happy", + slot: "Message", + round: "full", + hasCloseAction: true, + actionLabel: "Action", + status: "default", + actionClick: () => console.log("Action Clicked") + }, + parameters: { + design: { + url: "https://www.figma.com/file/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?type=design&node-id=21816-4283&m=dev&t=lGrwQ4pCwYESXz6b-4" + }, + status: { + type: "ready" + } + }, + argTypes: { + slotIcon: { + options: [...ZetaIconNameList], + control: { + type: "select" + } + }, + round: { + options: ["full", true, false], + control: { + type: "inline-radio" + } + }, + status: { + options: ["default", "positive", "info", "warning", "negative", "view"], + control: { + type: "inline-radio" + } + }, + rounded: { + table: { disable: true } + }, + actionClick: { + table: { disable: true } + } + } +}; + +export default meta; + +// canvas code block does not show props in snippit. +export const Snackbar: StoryObj<ZetaSnackbar | any> = { + render: args => html` + <zeta-snackbar + .actionClick=${() => console.log("Action Clicked")} + actionLabel=${args.actionLabel} + ?hasCloseAction=${args.hasCloseAction} + status=${args.status} + round=${args.round} + > + <zeta-icon slot="icon">${args.slotIcon}</zeta-icon> + ${args.slot} + </zeta-snackbar> + + <zeta-button + flavor="primary" + rounded + @click=${() => { + const snackbar = document.querySelector("zeta-snackbar"); + const button = document.querySelector("zeta-button"); + const animateIn = [{ transform: "translateY(0px)" }]; + const animateOut = [{ transform: "translateY(-100px)" }]; + + if (snackbar && button) { + button.disabled = true; + snackbar.animate(animateOut, { duration: 0, iterations: 1, fill: "forwards" }); + snackbar.animate(animateIn, { delay: 500, duration: 1000, iterations: 1, fill: "forwards", easing: "ease-in-out" }); + snackbar.animate(animateOut, { delay: 3000, duration: 1000, iterations: 1, fill: "forwards", easing: "ease-in-out" }); + setTimeout(() => { + snackbar.animate(animateIn, { duration: 0, iterations: 1, fill: "forwards" }); + button.disabled = false; + }, 5500); + } + }} + style="margin: 30px" + >Demo</zeta-button + > + ` +}; diff --git a/src/stories/Tab Bar/Docs.mdx b/src/stories/Tab Bar/Docs.mdx new file mode 100644 index 0000000..9dea8a3 --- /dev/null +++ b/src/stories/Tab Bar/Docs.mdx @@ -0,0 +1,16 @@ +import { Canvas, Meta, Description, ArgTypes, Title } from "@storybook/blocks"; +import * as TabBar from "./tab-bar.stories"; +import * as Items from "./tab-item.stories"; + +<Meta of={TabBar} title="Tab Bar" /> + +<Title /> +<Description of="zeta-tab-bar" /> +<Canvas of={TabBar.TabBar} /> +<ArgTypes of="zeta-tab-bar" /> + +# Header + +<Description of="zeta-tab-item" /> +<Canvas of={Items.Item} /> +<ArgTypes of="zeta-tab-item" /> diff --git a/src/stories/Tab Bar/tab-bar.stories.ts b/src/stories/Tab Bar/tab-bar.stories.ts new file mode 100644 index 0000000..0f9cba7 --- /dev/null +++ b/src/stories/Tab Bar/tab-bar.stories.ts @@ -0,0 +1,31 @@ +import type { Meta, StoryObj } from "@storybook/web-components"; +import { html } from "lit"; +import { ZetaTabBar } from "../../components/tab-bar/tab-bar.js"; +import "../../components/tab-bar/tab-item/tab-item.js"; + +//TODO: Component seems to be removed from figma. +const meta: Meta<ZetaTabBar> = { + component: "zeta-tab-bar", + title: "Tab Bar", + parameters: { + design: { + url: "https://www.figma.com/design/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?node-id=229-18" + }, + status: { + type: "ready" + } + } +}; +export default meta; + +export const TabBar: StoryObj<ZetaTabBar> = { + render: () => + html`<zeta-tab-bar> + <zeta-tab-item active>Menu Item</zeta-tab-item> + <zeta-tab-item>Menu Item</zeta-tab-item> + <zeta-tab-item>Menu Item</zeta-tab-item> + <zeta-tab-item>Menu Item</zeta-tab-item> + <zeta-tab-item>Menu Item</zeta-tab-item> + <zeta-tab-item disabled>Menu Item</zeta-tab-item> + </zeta-tab-bar>` +}; diff --git a/src/stories/Tab Bar/tab-item.stories.ts b/src/stories/Tab Bar/tab-item.stories.ts new file mode 100644 index 0000000..9f8ff84 --- /dev/null +++ b/src/stories/Tab Bar/tab-item.stories.ts @@ -0,0 +1,19 @@ +import type { Meta, StoryObj } from "@storybook/web-components"; +import { html } from "lit"; +import { ZetaTabItem } from "../../components/tab-bar/tab-item/tab-item.js"; + +const meta: Meta<ZetaTabItem> = { + component: "zeta-tab-item", + title: "Tab Bar", + args: { + rounded: true, + disabled: false, + active: false + }, + argTypes: {} +}; +export default meta; + +export const Item: StoryObj<ZetaTabItem> = { + render: args => html`<zeta-tab-item .rounded=${args.rounded} ?disabled=${args.disabled} ?active=${args.active}>Menu Item</zeta-tab-item>` +}; diff --git a/src/stories/Top Appbar/Docs.mdx b/src/stories/Top Appbar/Docs.mdx new file mode 100644 index 0000000..340eda9 --- /dev/null +++ b/src/stories/Top Appbar/Docs.mdx @@ -0,0 +1,39 @@ +import { Canvas, Meta, Description, ArgTypes, Title } from "@storybook/blocks"; +import * as Appbar from "./top-appbar.stories"; + +<Meta of={Appbar} title="Top Appbar" /> + +<Title /> +<Canvas of={Appbar.Default} /> +<Description of="zeta-top-appbar" /> +<ArgTypes of={Appbar} /> + +# Default + +Content can be added to appbars by slotting content in either the default, leading, or trailing slots. + +<Canvas of={Appbar.Default} /> + +# Centered + +The `centered` property can be used to center content in the appbar. + +<Canvas of={Appbar.Centered} /> + +# Contextual + +This is an example of a contextual appbar, which is used to display contextual actions. + +<Canvas of={Appbar.Contextual} /> + +# Search + +Search appbars can be created by slotting a `zeta-search` component into the default slot. + +<Canvas of={Appbar.Search} /> + +# Extended + +Use the `extended` property to put the title content below the main appbar. + +<Canvas of={Appbar.Extended} /> diff --git a/src/stories/Top Appbar/top-appbar.stories.ts b/src/stories/Top Appbar/top-appbar.stories.ts new file mode 100644 index 0000000..591d2c9 --- /dev/null +++ b/src/stories/Top Appbar/top-appbar.stories.ts @@ -0,0 +1,150 @@ +import type { Meta, StoryObj } from "@storybook/web-components"; +import type { ZetaTopAppbar } from "../../components/top-appbar/top-appbar"; +import "../../components/top-appbar/top-appbar"; +import { html } from "lit"; +import "../../components/icon/icon"; +import "../../components/avatar/avatar"; +import { styleMap } from "lit/directives/style-map.js"; +import "../../components/search/search"; + +type AppbarStory = ZetaTopAppbar & { leading: string; trailing: string }; + +const meta: Meta<ZetaTopAppbar> = { + component: "zeta-top-appbar", + title: "Top Appbar", + parameters: { + design: { + url: "https://www.figma.com/design/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?node-id=24183-7801&t=8Ym0ztJUuB4iD83l-4" + }, + status: { + type: "ready" + } + } +}; + +export default meta; + +export const Default: StoryObj<AppbarStory> = { + argTypes: { + centered: { table: { disable: true } }, + extended: { table: { disable: true } }, + slot: { table: { disable: true } }, + leading: { table: { disable: true } }, + trailing: { table: { disable: true } } + }, + render: () => + html`<zeta-top-appbar> + <zeta-icon slot="leading">hamburger_menu</zeta-icon> + <div style=${styleMap({ + display: "flex", + gap: "8px", + alignItems: "center" + })}> + <zeta-avatar size="xxxs"><img src="https://tinyurl.com/yn89fmc4"></img></zeta-avatar> + Title + </div> + <div slot="trailing" style=${styleMap({ + display: "flex", + gap: "16px", + alignItems: "center" + })}> + <zeta-icon>world</zeta-icon> + <zeta-icon>love</zeta-icon> + <zeta-icon>more_vertical</zeta-icon> + </div> + </zeta-top-appbar>` +}; + +export const Centered: StoryObj<AppbarStory> = { + argTypes: { + centered: { table: { disable: true } }, + extended: { table: { disable: true } }, + slot: { table: { disable: true } }, + leading: { table: { disable: true } }, + trailing: { table: { disable: true } } + }, + render: () => + html`<zeta-top-appbar centered> + <zeta-icon slot="leading">hamburger_menu</zeta-icon> + Title + <zeta-icon slot="trailing">user_circle</zeta-icon> + </zeta-top-appbar>` +}; + +export const Contextual: StoryObj<AppbarStory> = { + argTypes: { + centered: { table: { disable: true } }, + extended: { table: { disable: true } }, + slot: { table: { disable: true } }, + leading: { table: { disable: true } }, + trailing: { table: { disable: true } } + }, + render: () => + html`<zeta-top-appbar> + <zeta-icon slot="leading">close</zeta-icon> + 2 Items + <div + slot="trailing" + style=${styleMap({ + display: "flex", + gap: "16px", + alignItems: "center" + })} + > + <zeta-icon>edit</zeta-icon> + <zeta-icon>share</zeta-icon> + <zeta-icon>delete</zeta-icon> + <zeta-icon>more_vertical</zeta-icon> + </div> + </zeta-top-appbar>` +}; + +export const Search: StoryObj<AppbarStory> = { + argTypes: { + centered: { table: { disable: true } }, + extended: { table: { disable: true } }, + slot: { table: { disable: true } }, + leading: { table: { disable: true } }, + trailing: { table: { disable: true } } + }, + render: () => + html`<zeta-top-appbar> + <zeta-icon slot="leading">arrow_back</zeta-icon> + <zeta-search hasIcon placeholder="Search"></zeta-search> + </zeta-top-appbar>` +}; + +export const Extended: StoryObj<AppbarStory> = { + argTypes: { + centered: { table: { disable: true } }, + extended: { table: { disable: true } }, + slot: { table: { disable: true } }, + leading: { table: { disable: true } }, + trailing: { table: { disable: true } } + }, + render: () => + html`<zeta-top-appbar extended> + <zeta-icon slot="leading">hamburger_menu</zeta-icon> + <div + style=${styleMap({ + display: "flex", + gap: "8px", + alignItems: "center" + })} + > + Large Title + </div> + <div + slot="trailing" + style=${styleMap({ + display: "flex", + gap: "16px", + alignItems: "center" + })} + > + <zeta-icon>world</zeta-icon> + <zeta-icon>love</zeta-icon> + <zeta-icon>more_vertical</zeta-icon> + </div> + </zeta-top-appbar>` +}; diff --git a/src/stories/accordion.stories.ts b/src/stories/accordion.stories.ts new file mode 100644 index 0000000..7c0d22a --- /dev/null +++ b/src/stories/accordion.stories.ts @@ -0,0 +1,44 @@ +import type { Meta, StoryObj } from "@storybook/web-components"; +import { ZetaAccordion } from "../components/accordion/accordion.js"; +import { html } from "lit"; +import { ifDefined } from "lit/directives/if-defined.js"; + +const meta: Meta<ZetaAccordion> = { + component: "zeta-accordion", + title: "Accordion", + tags: ["autodocs"], + args: { + accordionTitle: "Title", + disabled: false, + open: false, + rounded: false, + contained: false + }, + parameters: { + design: { + url: "https://www.figma.com/file/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?type=design&node-id=229-3" + }, + status: { + type: "designPending" + } + } +}; + +export default meta; + +export const Accordion: StoryObj<ZetaAccordion> = { + render: args => { + return html`<zeta-accordion + accordionTitle=${ifDefined(args.accordionTitle)} + .disabled=${args.disabled} + .open=${args.open} + .rounded=${args.rounded} + .contained=${args.contained} + > + <li>Item one</li> + <li>Item two</li> + <li>Item three</li> + <li>Item four</li> + </zeta-accordion>`; + } +}; diff --git a/src/stories/avatar-rail.stories.ts b/src/stories/avatar-rail.stories.ts new file mode 100644 index 0000000..821de83 --- /dev/null +++ b/src/stories/avatar-rail.stories.ts @@ -0,0 +1,39 @@ +import type { Meta, StoryObj } from "@storybook/web-components"; +import { ZetaAvatarRail } from "../components/avatar-rail/avatar-rail.js"; + +const meta: Meta<ZetaAvatarRail & { "show-close": boolean }> = { + component: "zeta-avatar-rail", + title: "Avatar Rail", + tags: ["autodocs"], + args: { + "show-close": false, + size: "m" + }, + argTypes: { + size: { + options: ["xxxs", "xxs", "xs", "s", "m", "l", "xl", "xxl", "xxxl"], + control: { + type: "select" + } + } + }, + parameters: { + design: { + url: "https://www.figma.com/design/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?node-id=21300-52147&t=8Ym0ztJUuB4iD83l-4" + }, + status: { + type: "ready" + } + } +}; + +export default meta; + +export const AvatarRail: StoryObj<ZetaAvatarRail> = { + args: { + slot: "<zeta-avatar>AD</zeta-avatar><zeta-avatar>AB</zeta-avatar><zeta-avatar>AC</zeta-avatar>" + }, + argTypes: { + slot: { table: { disable: true } } + } +}; diff --git a/src/stories/checkbox.stories.ts b/src/stories/checkbox.stories.ts new file mode 100644 index 0000000..9926b41 --- /dev/null +++ b/src/stories/checkbox.stories.ts @@ -0,0 +1,38 @@ +import type { Meta, StoryObj } from "@storybook/web-components"; +import { ZetaCheckbox } from "../components/checkbox/checkbox.js"; +import { spreadGenerator } from "./utils.js"; +import { html } from "lit"; +const spread = spreadGenerator(ZetaCheckbox); + +const meta: Meta<ZetaCheckbox> = { + component: "zeta-checkbox", + title: "Checkbox", + tags: ["autodocs"], + args: { + rounded: true, + disabled: false, + checked: false, + indeterminate: false, + slot: undefined, + name: "", + id: "" + }, + parameters: { + design: { + url: "https://www.figma.com/file/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?type=design&node-id=21510-54003&mode=design&t=DUHsS5bjWB5UW1iG-4" + }, + status: { + type: "ready" + } + } +}; + +export default meta; + +export const Checkbox: StoryObj<ZetaCheckbox> = {}; +export const CheckboxWithLabel: StoryObj<ZetaCheckbox> = { + args: { slot: "Checkbox Label" }, + render: ({ slot, ...args }) => { + return html` <zeta-checkbox ${spread(args)}>${slot}</zeta-checkbox> `; + } +}; diff --git a/src/stories/dialog.stories.ts b/src/stories/dialog.stories.ts new file mode 100644 index 0000000..294c381 --- /dev/null +++ b/src/stories/dialog.stories.ts @@ -0,0 +1,142 @@ +import type { Meta, StoryObj } from "@storybook/web-components"; +import { html, nothing } from "lit"; +import { ZetaDialog } from "../components/dialog/dialog.js"; +import { fn } from "@storybook/test"; +import "../components/dialog/dialog.js"; +import "../components/button/button.js"; +import "../components/icon/icon.js"; + +import { ZetaIconNameList, type ZetaIconName } from "@zebra-fed/zeta-icons"; + +const meta: Meta< + Omit<ZetaDialog, "icon"> & { + confirm: string; + cancel: string; + other: string; + icon?: ZetaIconName; + "--icon-color": String; + onOpen: () => void; + onClose: () => void; + onCancel: () => void; + } +> = { + component: "zeta-dialog", + title: "Dialog", + tags: ["autodocs"], + args: { + centered: false, + rounded: false, + title: "Title", + slot: "Lorem ipsum dolor sit amet, conse ctetur adipiscing elit, sed do eiusm od tempor incididunt ut labore et do lore magna aliquaa met, conse ctetur adipisc.", + confirm: "Confirm", + cancel: "Cancel", + other: "Learn more", + icon: "star", + size: "large", + onOpen: fn(), + onClose: fn(), + onCancel: fn() + }, + argTypes: { + size: { + options: ["small", "large"], + control: { type: "radio" } + }, + initialOpen: { + table: { disable: true } + }, + open: { + table: { disable: true } + }, + icon: { + options: [null, ...ZetaIconNameList], + control: { type: "select" } + }, + "--icon-color": { control: "color" } + }, + parameters: { + design: { + url: "https://www.figma.com/file/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?type=design&node-id=23144-119557&mode=design&t=DUHsS5bjWB5UW1iG-4" + }, + status: { + type: "ready" + } + } +}; + +export const Dialog: StoryObj = { + name: "Dialog preview", + + render: args => { + return html` + <style> + :root { + ${args["--icon-color"] && `--icon-color: ${args["--icon-color"]}`} ; + } + </style> + <div class="box"> + <zeta-dialog + id="dialog1" + .rounded=${args.rounded} + ?centered=${args.centered} + .initialOpen=${true} + .title=${args.title} + size=${args.size} + @open=${args.onOpen} + @close=${args.onClose} + @cancel=${args.onCancel} + > + ${args.slot} ${args.icon ? html`<zeta-icon slot="icon">${args.icon}</zeta-icon>` : nothing} + ${args.confirm && args.confirm.length > 0 ? html`<zeta-button slot="confirm">${args.confirm}</zeta-button>` : nothing} + ${args.cancel && args.cancel.length > 0 ? html`<zeta-button slot="cancel">${args.cancel}</zeta-button>` : nothing} + ${args.other && args.other.length > 0 ? html`<zeta-button slot="other">${args.other}</zeta-button>` : nothing} + </zeta-dialog> + </div> + `; + } +}; + +export const DialogOpen: StoryObj = { + name: "Trigger Dialog Opening", + + render: args => { + return html` + <style> + :root { + ${args["--icon-color"] && `--icon-color: ${args["--icon-color"]}`} ; + } + </style> + <div class="box"> + <zeta-button + @click=${() => { + (document.querySelector("#dialog1") as ZetaDialog)?.showModal(); + }} + ><zeta-icon>open</zeta-icon>Open Dialog as Modal</zeta-button + > + <zeta-button + @click=${() => { + (document.querySelector("#dialog1") as ZetaDialog)?.show(); + }} + ><zeta-icon>open</zeta-icon>Open Dialog</zeta-button + > + <zeta-dialog + id="dialog1" + .rounded=${args.rounded} + .centered=${args.centered} + .title=${args.title} + size=${args.size} + @open=${args.onOpen} + @close=${args.onClose} + @cancel=${args.onCancel} + > + ${args.slot} ${args.icon ? html`<zeta-icon slot="icon">${args.icon}</zeta-icon>` : nothing} + ${args.confirm && args.confirm.length > 0 ? html`<zeta-button slot="confirm">${args.confirm}</zeta-button>` : nothing} + ${args.cancel && args.cancel.length > 0 ? html`<zeta-button slot="cancel">${args.cancel}</zeta-button>` : nothing} + ${args.other && args.other.length > 0 ? html`<zeta-button slot="other">${args.other}</zeta-button>` : nothing} + </zeta-dialog> + </div> + `; + } +}; + +export default meta; diff --git a/src/stories/global-header.stories.ts b/src/stories/global-header.stories.ts new file mode 100644 index 0000000..f647bab --- /dev/null +++ b/src/stories/global-header.stories.ts @@ -0,0 +1,81 @@ +import type { Meta, StoryObj } from "@storybook/web-components"; +import { html } from "lit"; +import { ZetaGlobalHeader } from "../components/global-header/global-header.js"; +import "../components/button/icon-button/icon-button.js"; +import "../components/search/search.js"; +import "../components/navigation-profile/navigation-profile.js"; +import "../components/tab-bar/tab-bar.js"; +import "../components/tab-bar/tab-item/tab-item.js"; +import "../components/avatar/avatar.js"; + +const meta: Meta<ZetaGlobalHeader> = { + component: "zeta-global-header", + tags: ["autodocs"], + title: "Global Header", + args: { + headline: "Service Name", + rounded: true, + menuPosition: "inline" + }, + argTypes: { + menuPosition: { + options: ["inline", "below"], + control: { + type: "select" + } + } + }, + parameters: { + design: { + url: "https://www.figma.com/file/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?type=design&node-id=23144-118110&mode=design&t=DUHsS5bjWB5UW1iG-4" + }, + status: { + type: "needsAttention" + } + } +}; +export default meta; + +export const GlobalHeader: StoryObj = { + argTypes: { + menuPosition: { + table: { + disable: true + } + } + }, + render: args => { + return html`<zeta-global-header headline=${args.headline}> + <zeta-icon-button slot="leading">hamburger_menu</zeta-icon-button> + <zeta-search slot="trailing" size="large" round="full"></zeta-search> + <zeta-icon-button slot="trailing">alert</zeta-icon-button> + <zeta-icon-button slot="trailing">star</zeta-icon-button> + <div slot="trailing" class="divider"> </div> + <zeta-icon-button slot="trailing">apps</zeta-icon-button> + <zeta-navigation-profile slot="trailing" rounded> + <zeta-avatar slot="leading" size="s"></zeta-avatar> + My account + </zeta-navigation-profile> + </zeta-global-header>`; + } +}; + +export const WithMenuItems: StoryObj = { + render: args => + html`<zeta-global-header menuPosition=${args.menuPosition} headline=${args.headline}> + <zeta-icon-button slot="leading" flavor="text">apps</zeta-icon-button> + <zeta-tab-bar slot="navigation-menu"> + <zeta-tab-item active>Menu Item</zeta-tab-item> + <zeta-tab-item>Menu Item</zeta-tab-item> + <zeta-tab-item>Menu Item</zeta-tab-item> + <zeta-tab-item>Menu Item</zeta-tab-item> + </zeta-tab-bar> + <zeta-icon-button slot="trailing" flavor="text">star</zeta-icon-button> + <zeta-icon-button slot="trailing" flavor="text">star</zeta-icon-button> + <zeta-icon-button slot="trailing" flavor="text">star</zeta-icon-button> + <zeta-navigation-profile slot="trailing" rounded> + <zeta-avatar slot="leading" size="s"></zeta-avatar> + My account + </zeta-navigation-profile> + </zeta-global-header>` +}; diff --git a/src/stories/pagination.stories.ts b/src/stories/pagination.stories.ts new file mode 100644 index 0000000..68fc576 --- /dev/null +++ b/src/stories/pagination.stories.ts @@ -0,0 +1,31 @@ +import type { Meta, StoryObj } from "@storybook/web-components"; +import { ZetaPagination } from "../components/pagination/pagination.js"; +import { html } from "lit"; +import { spreadGenerator } from "./utils.js"; +const spread = spreadGenerator(ZetaPagination); + +const meta: Meta<ZetaPagination> = { + component: "zeta-pagination", + tags: ["autodocs"], + title: "Pagination", + args: { + rounded: false, + totalPages: 10, + currentPage: 1, + siblingCount: 1 + }, + parameters: { + design: { + url: "https://www.figma.com/file/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?type=design&node-id=1348-33899&mode=design&t=DUHsS5bjWB5UW1iG-4" + }, + status: { + type: "ready" + } + } +}; + +export default meta; + +export const Pagination: StoryObj = { + render: args => html`<zeta-pagination ${spread(args)}></zeta-pagination>` +}; diff --git a/src/stories/radio-button.stories.ts b/src/stories/radio-button.stories.ts new file mode 100644 index 0000000..3b88996 --- /dev/null +++ b/src/stories/radio-button.stories.ts @@ -0,0 +1,62 @@ +import type { Meta, StoryObj } from "@storybook/web-components"; +import { ZetaRadioButton } from "../components/radio-button/radio-button.js"; +import { html } from "lit"; +import { spreadGenerator } from "./utils.js"; +import "../components/button/button"; + +const spread = spreadGenerator(ZetaRadioButton); + +const meta: Meta<ZetaRadioButton> = { + component: "zeta-radio-button", + tags: ["autodocs"], + title: "Radio Button", + args: { + disabled: false, + checked: false, + name: "", + id: "" + }, + parameters: { + design: { + url: "https://www.figma.com/file/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?type=design&node-id=21510-54345&mode=design&t=DUHsS5bjWB5UW1iG-4" + }, + status: { + type: "ready" + } + } +}; + +export default meta; + +export const RadioButton: StoryObj<ZetaRadioButton> = {}; + +export const RadioButtonWithLabel: StoryObj<ZetaRadioButton> = { + args: { + slot: "Label" + }, + render: ({ slot, ...args }) => html`<zeta-radio-button ${spread(args)}>${slot}</zeta-radio-button>` +}; + +export const RadioButtonInForm: StoryObj<ZetaRadioButton> = { + args: { + name: "myRadioButton" + }, + render: () => html` + <form + @submit=${(ev: Event) => { + console.log("Submit", ev); + const data = new FormData(ev.target as HTMLFormElement); + console.log(Object.fromEntries(data)); + }} + > + <fieldset> + <zeta-radio-button name="choice">Yes</zeta-radio-button> + <zeta-radio-button name="choice" value="No">No</zeta-radio-button> + <label><input type="radio" name="choice" />Maybe</label> + <label><input type="radio" name="choice" value="N/A" />Not Applicable</label> + </fieldset> + <zeta-button type="submit">Submit</zeta-button> + <zeta-button type="reset">Reset</zeta-button> + </form> + ` +}; diff --git a/src/stories/search.stories.ts b/src/stories/search.stories.ts new file mode 100644 index 0000000..5defbc0 --- /dev/null +++ b/src/stories/search.stories.ts @@ -0,0 +1,44 @@ +import type { Meta, StoryObj } from "@storybook/web-components"; +import { ZetaSearch } from "../components/search/search.js"; +import "../components/search/search.js"; + +const meta: Meta<ZetaSearch> = { + tags: ["autodocs"], + title: "Search", + component: "zeta-search", + args: { + value: "Predefined search value", + disabled: false, + size: "medium", + round: "false", + // formAction: "https://google.com/search", // BK to @mikecoomber, I removed this to get the story working after search was changed to a FormField. + hasIcon: true + }, + argTypes: { + size: { + options: ["small", "medium", "large"], + control: { + type: "inline-radio" + } + }, + round: { + options: ["false", "true", "full"], + control: { + type: "inline-radio" + } + } + // onSubmit: { table: { disable: true } } // BK to @mikecoomber, I removed this to get the story working after search was changed to a FormField. + }, + parameters: { + design: { + url: "https://www.figma.com/file/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?type=design&node-id=21286-35997&mode=design&t=DUHsS5bjWB5UW1iG-4" + }, + status: { + type: "ready" + } + } +}; + +export const Search: StoryObj = {}; + +export default meta; diff --git a/src/stories/stepper-input.stories.ts b/src/stories/stepper-input.stories.ts new file mode 100644 index 0000000..87a12ff --- /dev/null +++ b/src/stories/stepper-input.stories.ts @@ -0,0 +1,34 @@ +import type { Meta, StoryObj } from "@storybook/web-components"; +import { ZetaStepperInput } from "../components/stepper-input/stepper-input.js"; + +const meta: Meta<ZetaStepperInput> = { + component: "zeta-stepper-input", + tags: ["autodocs"], + title: "Stepper Input", + args: { + min: 0, + max: 100, + rounded: false, + disabled: false, + size: "medium", + value: 1 + }, + argTypes: { + size: { + options: ["medium", "large"], + control: { type: "select" } + } + }, + parameters: { + design: { + url: "https://www.figma.com/file/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?type=design&node-id=21529-9963&mode=design&t=DUHsS5bjWB5UW1iG-4" + }, + status: { + type: "ready" + } + } +}; + +export const StepperInput: StoryObj<ZetaStepperInput> = {}; + +export default meta; diff --git a/src/stories/stepper.stories.ts b/src/stories/stepper.stories.ts new file mode 100644 index 0000000..0db74a6 --- /dev/null +++ b/src/stories/stepper.stories.ts @@ -0,0 +1,66 @@ +import type { Meta, StoryObj } from "@storybook/web-components"; +import { html } from "lit"; +import { spreadGenerator } from "./utils.js"; +const spread = spreadGenerator(ZetaStepper); +import { ZetaStepper } from "../components/stepper/stepper.js"; + +const meta: Meta<ZetaStepper> = { + component: "zeta-stepper", + tags: ["autodocs"], + title: "Stepper", + args: { bar: false, rounded: false, activeStep: 0 }, + argTypes: { + variant: { + table: { disable: true } + } + }, + parameters: { + design: { + url: "https://www.figma.com/design/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?node-id=3420-67488" + }, + status: { + type: "ready" + } + } +}; + +export default meta; + +export const Horizontal: StoryObj = { + render: args => { + return html` + <zeta-stepper ${spread(args)} variant="horizontal"> + <li data-title="title 1" data-label="label 1"></li> + <li data-title="title 2" data-label="label 2"></li> + <li data-title="title 3" data-label="label 3"></li> + </zeta-stepper> + `; + }, + parameters: { + design: { + url: "https://www.figma.com/file/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?type=design&node-id=21529-11408&mode=design&t=DUHsS5bjWB5UW1iG-4" + }, + status: { + type: "ready" + } + } +}; +export const Vertical: StoryObj = { + render: args => { + return html` + <zeta-stepper ${spread(args)} variant="vertical"> + <li data-title="title 1" data-label="label 1"></li> + <li data-title="title 2" data-label="label 2"></li> + <li data-title="title 3" data-label="label 3"></li> + </zeta-stepper> + `; + }, + parameters: { + design: { + url: "https://www.figma.com/file/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?type=design&node-id=21529-11531&mode=design&t=DUHsS5bjWB5UW1iG-4" + }, + status: { + type: "needsAttention" + } + } +}; diff --git a/src/stories/switch.stories.ts b/src/stories/switch.stories.ts new file mode 100644 index 0000000..9256899 --- /dev/null +++ b/src/stories/switch.stories.ts @@ -0,0 +1,105 @@ +import type { Meta, StoryObj } from "@storybook/web-components"; +import { ZetaSwitch } from "../components/switch/switch.js"; +import { ZetaIconNameList } from "@zebra-fed/zeta-icons"; +import { spreadGenerator } from "./utils.js"; +import { html } from "lit"; +import { fn } from "@storybook/test"; + +const spread = spreadGenerator(ZetaSwitch); + +const meta: Meta<ZetaSwitch> = { + component: "zeta-switch", + tags: ["autodocs"], + title: "Switch", + args: { + rounded: true, + disabled: false, + onclick: fn(), + onchange: fn(), + oninput: fn() + }, + argTypes: { + activeIcon: { + options: ZetaIconNameList, + control: { + type: "select" + } + }, + inactiveIcon: { + options: ZetaIconNameList, + control: { + type: "select" + } + } + }, + parameters: { + design: { + url: "https://www.figma.com/file/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?type=design&node-id=1153-26923&mode=design&t=DUHsS5bjWB5UW1iG-4" + }, + status: { + type: "ready" + } + } +}; + +export default meta; +export const SwitchDefault: StoryObj<ZetaSwitch> = {}; +export const SwitchMuteControl: StoryObj<ZetaSwitch> = { + args: { activeIcon: "volume_up", inactiveIcon: "volume_off" } +}; +export const SwitchDisabled: StoryObj<ZetaSwitch> = { + args: { disabled: true, activeIcon: "volume_up", inactiveIcon: "volume_off" } +}; + +export const SwitchCustomSize: StoryObj = { + args: { activeIcon: "microphone", inactiveIcon: "microphone_off" }, + render: args => { + return html`<style> + zeta-switch.custom-size { + --switch-height: 48px; + --switch-width: 96px; + --switch-thumb-size: 40px; + } + </style> + <zeta-switch class="custom-size" ${spread(args)}></zeta-switch> `; + } +}; +export const SausageSwitch: StoryObj = { + args: { activeIcon: "microphone", inactiveIcon: "microphone_off" }, + render: args => { + return html`<style> + zeta-switch.sausage { + --switch-height: 48px; + --switch-width: 512px; + --switch-thumb-size: 40px; + } + </style> + <zeta-switch class="sausage" ${spread(args)}></zeta-switch> `; + } +}; +export const PattySwitch: StoryObj = { + args: { activeIcon: "microphone", inactiveIcon: "microphone" }, + render: args => { + return html`<style> + zeta-switch.patty { + --switch-height: 48px; + --switch-width: 72px; + --switch-thumb-size: 40px; + --switch-icon-size: 12px; + } + </style> + <zeta-switch class="patty" ${spread(args)}></zeta-switch> `; + } +}; +//TODO % dont work +export const SwitchCustomIconSize: StoryObj = { + args: { activeIcon: "microphone", inactiveIcon: "microphone" }, + render: args => { + return html`<style> + zeta-switch.custom-icon-size { + --switch-icon-size: 12px; + } + </style> + <zeta-switch class="custom-icon-size" ${spread(args)}></zeta-switch> `; + } +}; diff --git a/src/stories/system-banner.stories.ts b/src/stories/system-banner.stories.ts new file mode 100644 index 0000000..b82d497 --- /dev/null +++ b/src/stories/system-banner.stories.ts @@ -0,0 +1,55 @@ +import type { Meta, StoryObj } from "@storybook/web-components"; +import { ZetaSystemBanner } from "../components/system-banner/system-banner.js"; +import { html, nothing } from "lit"; +import { ifDefined } from "lit/directives/if-defined.js"; +import { ZetaIconNameList, type ZetaIconName } from "@zebra-fed/zeta-icons"; +import "../components/system-banner/system-banner.js"; + +const meta: Meta<ZetaSystemBanner | { leadingIcon: ZetaIconName; trailingIcon: ZetaIconName }> = { + title: "System Banner", + tags: ["autodocs"], + parameters: { + layout: "fullscreen", + design: { + url: "https://www.figma.com/file/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?type=design&node-id=229-12" + }, + status: { + type: "ready" + } + }, + component: "zeta-system-banner", + args: { + text: "Banner title", + align: "center", + status: "default", + rounded: true, + leadingIcon: "star", + trailingIcon: "star" + }, + argTypes: { + status: { + options: ["default", "positive", "warning", "negative"], + control: { + type: "select" + } + }, + align: { + options: ["center", "start"], + control: { type: "radio" } + }, + leadingIcon: { options: [null, ...ZetaIconNameList], control: { type: "select" } }, + trailingIcon: { options: [null, ...ZetaIconNameList], control: { type: "select" } } + } +}; +export default meta; + +export const Banner: StoryObj = { + argTypes: { + slot: { table: { disable: true } } + }, + render: args => + html`<zeta-system-banner align=${ifDefined(args.align)} status=${ifDefined(args.status)} text=${ifDefined(args.text)} .rounded=${args.rounded}> + ${args.leadingIcon && args.leadingIcon.length > 1 ? html`<zeta-icon slot="leadingIcon">${args.leadingIcon}</zeta-icon>` : nothing} + ${args.trailingIcon && args.trailingIcon.length > 1 ? html`<zeta-icon slot="trailingIcon">${args.trailingIcon}</zeta-icon>` : nothing} + </zeta-system-banner>` +}; diff --git a/src/stories/text-input.stories.ts b/src/stories/text-input.stories.ts new file mode 100644 index 0000000..e963b2e --- /dev/null +++ b/src/stories/text-input.stories.ts @@ -0,0 +1,154 @@ +import type { Meta, StoryObj } from "@storybook/web-components"; +import { ZetaTextInput } from "../components/text-input/text-input.js"; +import { ZetaIconNameList } from "@zebra-fed/zeta-icons"; +import { html } from "lit"; +import { spreadGenerator } from "./utils.js"; +const spread = spreadGenerator(ZetaTextInput); + +//TODO: These are seperate on Figma, should be split here too? +const meta: Meta<ZetaTextInput> = { + tags: ["autodocs"], + title: "Text Input", + component: "zeta-text-input", + args: { + value: "", + label: "Label", + hintText: "hint", + error: false, + disabled: false, + errorText: "Error!", + rounded: true, + required: false, + prefix: "", + suffix: "" + }, + argTypes: { + type: { + options: ["text", "textarea", "password", "time", "date"], + control: { + type: "select" + } + }, + leadingIcon: { + options: ZetaIconNameList, + control: { + type: "select" + } + }, + trailingIcon: { + options: ZetaIconNameList, + control: { + type: "select" + } + }, + size: { + options: ["small", "medium", "large"], + control: { type: "inline-radio" } + } + }, + parameters: { + design: { + url: "https://www.figma.com/file/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?type=design&node-id=23116-92946&mode=design&t=DUHsS5bjWB5UW1iG-4" + }, + status: { + type: "ready" + } + } +}; +// console.log('Text-Input attributes:', ZetaTextInput.elementProperties); + +export const TextInput: StoryObj<ZetaTextInput> = { + name: "Default text input" +}; + +export const EmptyTextInput: StoryObj<ZetaTextInput> = { + name: "Empty", + render: _args => html` <zeta-text-input> </zeta-text-input> ` +}; + +export const TimeInput: StoryObj = { + name: "Time input", + args: { + type: "time" + }, + argTypes: { + hintText: { table: { disable: true } }, + placeholder: { table: { disable: true } }, + leadingIcon: { table: { disable: true } }, + trailingIcon: { table: { disable: true } }, + prefix: { table: { disable: true } }, + suffix: { table: { disable: true } } + }, + parameters: { + design: { + url: "https://www.figma.com/file/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?type=design&node-id=22751-9848&mode=design&t=DUHsS5bjWB5UW1iG-4" + }, + status: { + type: "designPending" + } + }, + render: args => html` <zeta-text-input ${spread(args)}> </zeta-text-input> ` +}; + +export const DateInput: StoryObj = { + name: "Date input", + args: { + type: "date" + }, + argTypes: { + hintText: { table: { disable: true } }, + placeholder: { table: { disable: true } }, + leadingIcon: { table: { disable: true } }, + trailingIcon: { table: { disable: true } }, + prefix: { table: { disable: true } }, + suffix: { table: { disable: true } } + }, + parameters: { + design: { + url: "https://www.figma.com/file/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?type=design&node-id=22667-52911&mode=design&t=DUHsS5bjWB5UW1iG-4" + }, + status: { + type: "designPending" + } + }, + render: args => html` <zeta-text-input ${spread(args)}> </zeta-text-input> ` +}; + +export const TextArea: StoryObj = { + name: "Text area field", + args: { + type: "textarea" + }, + argTypes: { + leadingIcon: { table: { disable: true } }, + trailingIcon: { table: { disable: true } }, + prefix: { table: { disable: true } }, + suffix: { table: { disable: true } } + }, + render: args => html` <zeta-text-input ${spread(args)}> </zeta-text-input> ` +}; + +export const PasswordField: StoryObj = { + name: "Password field", + args: { + type: "password" + }, + argTypes: { + leadingIcon: { table: { disable: true } }, + trailingIcon: { table: { disable: true } }, + placeholder: { table: { disable: true } }, + prefix: { table: { disable: true } }, + suffix: { table: { disable: true } } + }, + parameters: { + design: { + url: "https://www.figma.com/file/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?type=design&node-id=948-13632&mode=design&t=DUHsS5bjWB5UW1iG-4" + }, + status: { + type: "designPending" + } + }, + render: args => html` <zeta-text-input ${spread(args)}> </zeta-text-input> ` +}; + +export default meta; diff --git a/src/stories/tooltip.stories.ts b/src/stories/tooltip.stories.ts new file mode 100644 index 0000000..05e7d54 --- /dev/null +++ b/src/stories/tooltip.stories.ts @@ -0,0 +1,31 @@ +import type { Meta, StoryObj } from "@storybook/web-components"; +import { ZetaTooltip } from "../components/tooltip/tooltip.js"; + +const meta: Meta<ZetaTooltip> = { + component: "zeta-tooltip", + tags: ["autodocs"], + title: "Tooltip", + args: { + label: "Label", + point: "bottom", + rounded: false + }, + argTypes: { + point: { + options: ["left", "right", "bottom", "top"], + control: { type: "inline-radio" } + } + }, + parameters: { + design: { + url: "https://www.figma.com/file/JesXQFLaPJLc1BdBM4sisI/%F0%9F%A6%93-ZDS---Components?type=design&node-id=21816-222&mode=design&t=DUHsS5bjWB5UW1iG-4" + }, + status: { + type: "ready" + } + } +}; + +export default meta; + +export const Tooltip: StoryObj<ZetaTooltip> = {}; diff --git a/src/stories/utils.ts b/src/stories/utils.ts new file mode 100644 index 0000000..8b66e6e --- /dev/null +++ b/src/stories/utils.ts @@ -0,0 +1,34 @@ +import { spread as oldSpread } from "@open-wc/lit-helpers"; +import type { LitElement, PropertyDeclaration } from "lit"; +import type { DirectiveResult } from "lit/directive.js"; + +type SpreadData = { + [key: string]: unknown; +}; +type PropertyDeclarationMap = Map<string, PropertyDeclaration>; + +const _spread = (_spreadData: SpreadData, properties: PropertyDeclarationMap): DirectiveResult => { + const fixedSpreadData = Object.entries(_spreadData).reduce((acc, [key, value]) => { + const property = properties.get(key) as (PropertyDeclaration & { type?: { name: string } }) | undefined; + let newKey = key; + /* Future '@' will set the event */ + if (property?.attribute === undefined || property?.attribute !== false) { + newKey = `${property?.type && property.type.name == "Boolean" ? "?" : ""}${key}`; + } else if (property?.attribute === false) { + newKey = `.${key}`; + } + // Fix for css-variables, which don't have a type + if (property?.type == undefined) { + return acc; + } + acc[newKey] = value; + return acc; + }, {} as SpreadData); + const returnVal = oldSpread(fixedSpreadData) as SpreadData; + console.log("Spread", _spreadData, fixedSpreadData, properties, returnVal); + return returnVal; +}; + +export const spreadGenerator = (clas: typeof LitElement) => { + return (spreadData: SpreadData) => _spread(spreadData, clas.elementProperties as PropertyDeclarationMap); +}; diff --git a/src/styles.d.ts b/src/styles.d.ts new file mode 100644 index 0000000..bd10143 --- /dev/null +++ b/src/styles.d.ts @@ -0,0 +1,5 @@ +declare module "*.styles.js" { + import type { CSSResult } from "lit"; + const styles: CSSResult; + export default styles; +} diff --git a/src/test/.eslintrc b/src/test/.eslintrc new file mode 100644 index 0000000..571ed6c --- /dev/null +++ b/src/test/.eslintrc @@ -0,0 +1,62 @@ +{ + "root": true, + "env": { + "node": true, + "browser": true, + "es2023": true + }, + "extends": [ + "prettier", + "eslint:recommended", + "plugin:storybook/recommended", + "plugin:@typescript-eslint/recommended", + "plugin:@typescript-eslint/recommended-requiring-type-checking" + ], + "parser": "@typescript-eslint/parser", + "parserOptions": { + // "ecmaFeatures": { + // "jsx": true + // }, + "ecmaVersion": "latest", + "sourceType": "module", + "project": ["./tsconfig.json"] + }, + "plugins": ["@typescript-eslint", "wc", "eslint-plugin-tsdoc"], + "rules": { + "wc/attach-shadow-constructor": "error", + "wc/guard-super-call": "error", + "wc/no-closed-shadow-root": "error", + "wc/no-constructor-attributes": "error", + "wc/no-constructor-params": "error", + "wc/no-invalid-element-name": "error", + "wc/no-self-class": "error", + "wc/no-typos": "error", + "wc/require-listener-teardown": "error", + "camelcase": 1, + "comma-dangle": [1, "never"], + "eol-last": [0], + // "func-names": 0, + "indent": ["off", 2, { "SwitchCase": 1 }], + "no-extra-semi": "warn", + "no-extra-boolean-cast": "warn", + "no-undef": 0, + "no-unused-vars": "off", + "@typescript-eslint/no-unused-vars": [ + "warn", + { + "argsIgnorePattern": "^_", + "varsIgnorePattern": "^_" + } + ], + "semi": [1, "always"], + "spaced-comment": 0, + "@typescript-eslint/no-unsafe-member-access": "off", + "@typescript-eslint/no-unsafe-call": "off" + }, + "overrides":[{ + "files": ["src/**/*.ts"] + } +], +"settings": {}, +"ignorePatterns": ["/**/*.stories.*", "**/generated"] +} diff --git a/src/test/TESTING_README.md b/src/test/TESTING_README.md new file mode 100644 index 0000000..efc07c5 --- /dev/null +++ b/src/test/TESTING_README.md @@ -0,0 +1,82 @@ +# Testing Conventions Web Components + +### Groups + +- **Accessibility Tests** + Semantic labels, touch areas, contrast ratios, etc. + +- **Content Tests** + Finds the component, parameter statuses, etc. + Checking for the value of props and attributes of the component. Checking for the presence of sub-element. + +- **Dimensions Tests** + Size, padding, margin, alignment, etc. + +- **Styling Tests** + Rendered colors, fonts, borders, radii etc. + Checking the style of elements and child elements. + +- **Interaction Tests** + Gesture recognizers, taps, drags, etc. + For example, using a boolean to check if the elements interaction function runs. + +- **Golden Tests** + Compares the rendered component with the golden file. + +- **Performance Tests** + Animation performance, rendering performance, data manupulation performance, etc. + +### Rules + +- Test group describe blocks must be nested in "component" describe blocks `describe("replace-with-zeta-tag", () => {`. +- You can have multiple "component" describe blocks. +- Aim to keep nesting to a minimum. Use only the "component" and "category" describe blocks. Do not nest any further. +- Comment out unused describe blocks. + +### Keep in mind + +- You may need to reinitialise the component within the nested category describe block if you find percular results. + ``` + beforeEach(async () => { + subject = await createComponent(); + }); + ``` + Put this at the top of the troublesome category describe block. + +### Testing file template + +```ts +import { fixture, html, expect, unsafeStatic } from "@open-wc/testing"; +import type { ZETA_TYPE } from "PATH_TO_COMPONENT"; +import "PATH_TO_COMPONENT"; + +import "../../index.css"; + +describe("replace-with-zeta-tag", () => { + let subject: REPLACE_WITH_ZETA_TYPE; + + const createComponent = (template = `<zeta-element></zeta-element>`) => { + // prettier-ignore + return fixture<REPLACE_WITH_ZETA_TYPE>(html`${unsafeStatic(template)}`); + }; + + beforeEach(async () => { + subject = await createComponent(); + }); + + // Keep unnecessary + // describe("Accessibility Tests", () => {}); + + // describe("Content Tests", () => {}); + + // describe("Dimensions Tests", () => {}); + + // describe("Styling Tests", () => {}); + + // describe("Interaction Tests", () => {}); + + // describe("Golden Tests", () => {}); + + // describe("Performance Tests", () => {}); +}); +``` diff --git a/src/test/action-menu/__snapshots__/action-menu-button.test.snap.js b/src/test/action-menu/__snapshots__/action-menu-button.test.snap.js new file mode 100644 index 0000000..c1f3e8e --- /dev/null +++ b/src/test/action-menu/__snapshots__/action-menu-button.test.snap.js @@ -0,0 +1,25 @@ +/* @web/test-runner snapshot v1 */ +export const snapshots = {}; + +snapshots["zeta-action-menu-button renders the action menu button correctly"] = `<zeta-icon-button + flavor="primary" + id="anchor" + rounded="" + size="medium" +> + more_vertical +</zeta-icon-button> +<zeta-droppable + open="" + rounded="" + style="top: 48px; left: 8px;" +> + <zeta-dropdown-menu-item + icon="star" + rounded="" + > + Auto Item + </zeta-dropdown-menu-item> +</zeta-droppable> +`; +/* end snapshot zeta-action-menu-button renders the action menu button correctly */ diff --git a/src/test/action-menu/action-menu-button.test.ts b/src/test/action-menu/action-menu-button.test.ts new file mode 100644 index 0000000..54ad193 --- /dev/null +++ b/src/test/action-menu/action-menu-button.test.ts @@ -0,0 +1,112 @@ +import { fixture, html, expect, unsafeStatic } from "@open-wc/testing"; +import type { ZetaActionMenuButton } from "../../components/action-menu/action-menu-button.js"; +import type { ZetaIconButton } from "../../components/button/icon-button/icon-button.js"; +import "../../components/action-menu/action-menu-button.js"; + +describe("zeta-action-menu-button", () => { + let subject: ZetaActionMenuButton; + + const createComponent = ( + template = `<zeta-action-menu-button + size="medium" + rounded + icon="more_vertical" + alignment="start" + > + </zeta-action-menu-button>` + ) => { + // prettier-ignore + return fixture<ZetaActionMenuButton>(html`${unsafeStatic(template)}`); + }; + + beforeEach(async () => { + subject = await createComponent(); + }); + + describe("Accessibility Tests", () => { + it("meets accessability requirements", async () => { + await expect(subject).shadowDom.to.be.accessible(); + }); + }); + + // describe("Content Tests", () => { + // it.skip("renders an icon with the correct name", async () => { + // // fails due to slotting icon changes + // const iconName = "more_vertical"; + // const iconElement = subject.shadowRoot!.querySelector("zeta-icon-button") as ZetaIconButton; + // expect(iconElement).to.not.be.undefined; + // await expect(getIconName(iconElement)).to.equal(iconName); + // }); + // }); + + describe("Dimensions Tests", () => { + it("aligns the zeta droppable to the start", async () => { + (subject.shadowRoot?.querySelector("zeta-icon-button") as ZetaIconButton).click(); + await subject.updateComplete; + + const dropdownMenuButtonRect = subject.getBoundingClientRect(); + const droppableRect = subject.droppable.getBoundingClientRect(); + + const dropdownMenuButtonStart = dropdownMenuButtonRect.left; + const droppableStart = droppableRect.left; + + await expect(droppableStart).to.equal(dropdownMenuButtonStart); + }); + + it("aligns the zeta droppable to the center", async () => { + subject.alignment = "center"; + (subject.shadowRoot?.querySelector("zeta-icon-button") as ZetaIconButton).click(); + await subject.updateComplete; + + const dropdownMenuButtonRect = subject.getBoundingClientRect(); + const droppableRect = subject.droppable.getBoundingClientRect(); + + const dropdownMenuButtonCenter = dropdownMenuButtonRect.left + dropdownMenuButtonRect.width / 2; + const droppableCenter = droppableRect.left + droppableRect.width / 2; + + expect(droppableCenter).to.be.closeTo(dropdownMenuButtonCenter, 1); + }); + + it("aligns the zeta droppable to the end", async () => { + subject.alignment = "end"; + (subject.shadowRoot?.querySelector("zeta-icon-button") as ZetaIconButton).click(); + await subject.updateComplete; + + const dropdownMenuButtonRect = subject.getBoundingClientRect(); + const droppableRect = subject.droppable.getBoundingClientRect(); + + const dropdownMenuButtonEnd = dropdownMenuButtonRect.right; + const droppableEnd = droppableRect.right; + + await expect(droppableEnd).to.equal(dropdownMenuButtonEnd); + }); + }); + + // describe("Styling Tests", () => {}); + + describe("Interaction Tests", () => { + it("opens the action menu when clicked", async () => { + (subject.shadowRoot?.querySelector("zeta-icon-button") as ZetaIconButton).click(); + await subject.updateComplete; + + expect(subject.open).to.be.true; + }); + it("closes the dropdown menu when clicked outside", async () => { + (subject.shadowRoot?.querySelector("zeta-icon-button") as ZetaIconButton).click(); + await subject.updateComplete; + + document.body.click(); + await subject.updateComplete; + + expect(subject.open).to.be.false; + }); + }); + + // describe("Golden Tests", () => { + // it("renders the action menu button correctly", () => { + // expect(subject).shadowDom.to.equalSnapshot(); + // }); + // }); + + // describe("Performance Tests", () => {}); +}); diff --git a/src/test/appbar/appbar.test.ts b/src/test/appbar/appbar.test.ts new file mode 100644 index 0000000..9b64515 --- /dev/null +++ b/src/test/appbar/appbar.test.ts @@ -0,0 +1,77 @@ +import { fixture, html, expect } from "@open-wc/testing"; +import type { ZetaTopAppbar } from "../../components/top-appbar/top-appbar.js"; +import "../../components/top-appbar/top-appbar.js"; + +describe("zeta-top-appbar", () => { + // let subject: ZetaAppbar; + + // const createComponent = (template = `<zeta-top-appbar></zeta-top-appbar>`) => { + // // prettier-ignore + // return fixture<ZetaAppbar>(html`${unsafeStatic(template)}`); + // }; + + // beforeEach(async () => { + // subject = await createComponent(); + // }); + + // describe("Accessibility Tests", () => {}); + + describe("Content Tests", () => { + it("renders the appbar with default values", async () => { + const appbar: ZetaTopAppbar = await fixture(html`<zeta-top-appbar></zeta-top-appbar>`); + expect(appbar.centered).to.be.false; + expect(appbar.extended).to.be.false; + }); + + it("renders the appbar with centered title", async () => { + const appbar: ZetaTopAppbar = await fixture(html`<zeta-top-appbar centered></zeta-top-appbar>`); + expect(appbar.centered).to.be.true; + expect(appbar.extended).to.be.false; + }); + + it("renders the appbar with extended title", async () => { + const appbar: ZetaTopAppbar = await fixture(html`<zeta-top-appbar extended></zeta-top-appbar>`); + expect(appbar.centered).to.be.false; + expect(appbar.extended).to.be.true; + }); + + it("renders content in the appbars leading slot", async () => { + const appbar = await fixture(html` + <zeta-top-appbar> + <div slot="leading">Leading content</div> + </zeta-top-appbar> + `); + + const slot: HTMLSlotElement | null | undefined = appbar.shadowRoot?.querySelector("#leading-slot"); + expect(slot?.assignedElements()).to.not.be.empty; + }); + + it("renders content in the appbars trailing slot", async () => { + const appbar = await fixture(html` + <zeta-top-appbar> + <div slot="trailing">trailing content</div> + </zeta-top-appbar> + `); + + const slot: HTMLSlotElement | null | undefined = appbar.shadowRoot?.querySelector("#trailing-slot"); + expect(slot?.assignedElements()).to.not.be.empty; + }); + + it("renders content in the appbar", async () => { + const appbar = await fixture(html` <zeta-top-appbar> <div>Title</div> </zeta-top-appbar> `); + + const slot: HTMLSlotElement | null | undefined = appbar.shadowRoot?.querySelector("#content-slot"); + expect(slot?.assignedElements()).to.not.be.empty; + }); + }); + + // describe("Dimensions Tests", () => {}); + + // describe("Styling Tests", () => {}); + + // describe("Interaction Tests", () => {}); + + // describe("Golden Tests", () => {}); + + // describe("Performance Tests", () => {}); +}); diff --git a/src/test/avatar/avatar.test.ts b/src/test/avatar/avatar.test.ts new file mode 100644 index 0000000..1ea00f8 --- /dev/null +++ b/src/test/avatar/avatar.test.ts @@ -0,0 +1,87 @@ +import { fixture, html, expect } from "@open-wc/testing"; +import type { ZetaAvatar } from "../../components/avatar/avatar.js"; +import "../../components/avatar/avatar.js"; + +import type { ZetaIcon } from "../../components/icon/icon.js"; +import type { ZetaIconIndicator } from "../../components/badges/indicators/indicators.js"; +import { getSlotText } from "../utils.js"; + +describe("zeta-avatar", () => { + // let subject: ZetaAvatar; + + // const createComponent = (template = `<zeta-avatar></zeta-avatar>`) => { + // // prettier-ignore + // return fixture<ZetaAvatar>(html`${unsafeStatic(template)}`); + // }; + + // beforeEach(async () => { + // subject = await createComponent(); + // }); + + // describe("Accessibility Tests", () => {}); + + describe("Content Tests", () => { + it("renders the avatar with a custom size", async () => { + const avatar: ZetaAvatar = await fixture(html`<zeta-avatar size="xl"></zeta-avatar>`); + await expect(avatar.size).to.equal("xl"); + }); + + it("renders the avatar with the ring", async () => { + const avatar: ZetaAvatar = await fixture(html`<zeta-avatar show-ring></zeta-avatar>`); + return expect(avatar.showRing).to.be.true; + }); + + it("renders the avatar without the ring", async () => { + const avatar: ZetaAvatar = await fixture(html`<zeta-avatar></zeta-avatar>`); + return expect(avatar.showRing).to.be.false; + }); + + it("renders the avatar with the close icon", async () => { + const avatar: ZetaAvatar = await fixture(html`<zeta-avatar show-close></zeta-avatar>`); + return expect(avatar.showClose).to.be.true; + }); + + it("renders the avatar without the close icon", async () => { + const avatar: ZetaAvatar = await fixture(html`<zeta-avatar></zeta-avatar>`); + return expect(avatar.showClose).to.be.false; + }); + + it("renders the avatar with an image", async () => { + const url = "https://example.com/image.jpg"; + const avatar: ZetaAvatar = await fixture(html`<zeta-avatar><img src=${url} /></zeta-avatar>`); + const slot: HTMLSlotElement | null | undefined = avatar.shadowRoot?.querySelector("#CONTENT_SLOT"); + const img = slot?.assignedElements()[0] as HTMLImageElement; + + expect(img).to.exist; + return await expect(img?.src).to.equal(url); + }); + + it("renders the avatar with an icon", async () => { + const iconName = "user"; + const avatar: ZetaAvatar = await fixture(html`<zeta-avatar><zeta-icon>${iconName}</zeta-icon></zeta-avatar>`); + const slot: HTMLSlotElement | null | undefined = avatar.shadowRoot?.querySelector("#CONTENT_SLOT"); + const icon = slot?.assignedElements()[0] as ZetaIcon; + + expect(icon).to.exist; + await expect(getSlotText(icon)).to.equal(iconName); + }); + + it("renders the badge on the avatar", async () => { + const avatar: ZetaAvatar = await fixture(html`<zeta-avatar><zeta-icon-indicator icon="star" slot="status"></zeta-icon-indicator></zeta-avatar>`); + const slot: HTMLSlotElement | null | undefined = avatar.shadowRoot?.querySelector("#STATUS_SLOT"); + + const badge = slot?.assignedElements()[0] as ZetaIconIndicator; + expect(badge).to.exist; + }); + }); + + // describe("Dimensions Tests", () => {}); + + // describe("Styling Tests", () => {}); + + // describe("Interaction Tests", () => {}); + + // describe("Golden Tests", () => {}); + + // describe("Performance Tests", () => {}); +}); diff --git a/src/test/bottom-sheets/bottom-sheet.test.ts b/src/test/bottom-sheets/bottom-sheet.test.ts new file mode 100644 index 0000000..3effbfa --- /dev/null +++ b/src/test/bottom-sheets/bottom-sheet.test.ts @@ -0,0 +1,263 @@ +import { fixture, html, expect, unsafeStatic, elementUpdated } from "@open-wc/testing"; +import type { ZetaBottomSheet } from "../../components/bottom-sheets/bottom-sheet.js"; +import "../../components/bottom-sheets/bottom-sheet.js"; +const headerText = "Title"; + +describe("zeta-bottom-sheet + zeta-list-item", () => { + let subject: ZetaBottomSheet; + + const createComponent = ( + template = `<zeta-bottom-sheet headerText=${headerText}> + <zeta-list-item headline=${"Item 1"}></zeta-list-item> + <zeta-list-item headline=${"Item 2"}></zeta-list-item> + <zeta-list-item headline=${"Item 3"}></zeta-list-item> + </zeta-bottom-sheet>` + ) => { + // prettier-ignore + return fixture<ZetaBottomSheet>(html`${unsafeStatic(template)}`); + }; + + beforeEach(async () => { + subject = await createComponent(); + }); + + describe("Accessibility Tests", () => { + it("it meets accessibility requirements", async () => { + await expect(subject).shadowDom.to.be.accessible(); + }); + }); + + describe("Content Tests", () => { + it("renders the bottom sheet", () => { + expect(subject).to.exist; + }); + + it("displays the header text correctly", async () => { + const headerText = "Custom Header Text"; + subject.headerText = headerText; + await elementUpdated(subject); + + const headerElement = subject.shadowRoot?.querySelector(".header"); + await expect(headerElement?.textContent).to.equal(headerText); + }); + + it("updates when isExpanded changes", async () => { + subject.isExpanded = true; + await elementUpdated(subject); + + await expect(subject.isExpanded).to.not.equal(false); + }); + }); + + describe("Dimensions Tests", () => { + it("applies the correct header alignment", async () => { + const headerAlignment = "center"; + subject.headerAlignment = headerAlignment; + await elementUpdated(subject); + + const headerElement = subject.shadowRoot?.querySelector(".header"); + await expect(getComputedStyle(headerElement!).justifyContent).to.equal(headerAlignment); + + const headerAlignmentStart = "start"; + subject.headerAlignment = headerAlignmentStart; + await elementUpdated(subject); + + await expect(getComputedStyle(headerElement!).justifyContent).to.equal("flex-" + headerAlignmentStart); + }); + + it("applies the correct css display for list items", async () => { + const sheetContent = subject.shadowRoot?.querySelector(".content"); + await expect(getComputedStyle(sheetContent!).display).to.equal("flex"); + }); + }); + + // describe("Styling Tests", () => {}); + + // describe("Interaction Tests", () => {}); + + // describe("Golden Tests", () => {}); + + // describe("Performance Tests", () => {}); +}); + +describe("zeta-bottom-sheet + zeta-grid-menu-item", () => { + let subject: ZetaBottomSheet; + + const createComponent = ( + template = `<zeta-bottom-sheet headerText=${headerText}> + <zeta-grid-menu-item>Item 1</zeta-grid-menu-item> + <zeta-grid-menu-item>Item 2</zeta-grid-menu-item> + <zeta-grid-menu-item>Item 3</zeta-grid-menu-item> + <zeta-grid-menu-item>Item 4</zeta-grid-menu-item> + </zeta-bottom-sheet>` + ) => { + // prettier-ignore + return fixture<ZetaBottomSheet>(html`${unsafeStatic(template)}`); + }; + + beforeEach(async () => { + subject = await createComponent(); + }); + + describe("Accessibility Tests", () => { + it("it meets accessibility requirements", async () => { + await expect(subject).shadowDom.to.be.accessible(); + }); + }); + + describe("Content Tests", () => { + it("renders the bottom sheet", () => { + expect(subject).to.exist; + }); + + it("displays the header text correctly", async () => { + const headerText = "Custom Header Text"; + subject.headerText = headerText; + await elementUpdated(subject); + + const headerElement = subject.shadowRoot?.querySelector(".header"); + await expect(headerElement?.textContent).to.equal(headerText); + }); + + it("updates when isExpanded changes", async () => { + subject.isExpanded = true; + await elementUpdated(subject); + + await expect(subject.isExpanded).to.not.equal(false); + }); + }); + + describe("Dimensions Tests", () => { + it("applies the correct header alignment", async () => { + const headerAlignment = "center"; + subject.headerAlignment = headerAlignment; + await elementUpdated(subject); + + const headerElement = subject.shadowRoot?.querySelector(".header"); + await expect(getComputedStyle(headerElement!).justifyContent).to.equal(headerAlignment); + + const headerAlignmentStart = "start"; + subject.headerAlignment = headerAlignmentStart; + await elementUpdated(subject); + + await expect(getComputedStyle(headerElement!).justifyContent).to.equal("flex-" + headerAlignmentStart); + }); + + it("applies the correct css display for grid items", async () => { + subject.isGrid = true; + await elementUpdated(subject); + + const sheetContent = subject.shadowRoot?.querySelector(".content"); + await expect(getComputedStyle(sheetContent!).display).to.equal("grid"); + }); + }); + + // describe("Styling Tests", () => {}); + + // describe("Interaction Tests", () => {}); + + // describe("Golden Tests", () => {}); + + // describe("Performance Tests", () => {}); +}); + +describe("zeta-bottom-sheet + generic content", () => { + let subject: ZetaBottomSheet; + + const createComponent = ( + template = `<zeta-bottom-sheet headerText=${headerText}> + <div> + <h1>Generic Content</h1> + <p>This is generic content</p> + <div> + <h2>Section 1</h2> + <p>This is the first section of the generic content.</p> + </div> + <div> + <h2>Section 2</h2> + <p>This is the second section of the generic content.</p> + </div> + <div> + <h2>Section 3</h2> + <p>This is the third section of the generic content.</p> + </div> + <div> + <h2>Section 4</h2> + <p>This is the fourth section of the generic content.</p> + </div> + <div> + <h2>Section 5</h2> + <p>This is the fifth section of the generic content.</p> + </div> + </div> + </zeta-bottom-sheet>` + ) => { + // prettier-ignore + return fixture<ZetaBottomSheet>(html`${unsafeStatic(template)}`); + }; + + beforeEach(async () => { + subject = await createComponent(); + }); + + describe("Accessibility Tests", () => { + it("it meets accessibility requirements", async () => { + await expect(subject).shadowDom.to.be.accessible(); + }); + }); + + describe("Content Tests", () => { + it("renders the bottom sheet", () => { + expect(subject).to.exist; + }); + + it("displays the header text correctly", async () => { + const headerText = "Custom Header Text"; + subject.headerText = headerText; + await elementUpdated(subject); + + const headerElement = subject.shadowRoot?.querySelector(".header"); + await expect(headerElement?.textContent).to.equal(headerText); + }); + + it("updates when isExpanded changes", async () => { + subject.isExpanded = true; + await elementUpdated(subject); + + await expect(subject.isExpanded).to.not.equal(false); + }); + }); + + describe("Dimensions Tests", () => { + it("applies the correct header alignment", async () => { + const headerAlignment = "center"; + subject.headerAlignment = headerAlignment; + await elementUpdated(subject); + + const headerElement = subject.shadowRoot?.querySelector(".header"); + await expect(getComputedStyle(headerElement!).justifyContent).to.equal(headerAlignment); + + const headerAlignmentStart = "start"; + subject.headerAlignment = headerAlignmentStart; + await elementUpdated(subject); + + await expect(getComputedStyle(headerElement!).justifyContent).to.equal("flex-" + headerAlignmentStart); + }); + + it("applies the correct css display for generic content", async () => { + subject.isGenericContent = true; + await elementUpdated(subject); + + const sheetContent = subject.shadowRoot?.querySelector(".content"); + await expect(getComputedStyle(sheetContent!).display).to.equal("block"); + }); + }); + + // describe("Styling Tests", () => {}); + + // describe("Interaction Tests", () => {}); + + // describe("Golden Tests", () => {}); + + // describe("Performance Tests", () => {}); +}); diff --git a/src/test/breadcrumbs/breadcrumb.test.ts b/src/test/breadcrumbs/breadcrumb.test.ts new file mode 100644 index 0000000..f26ca2b --- /dev/null +++ b/src/test/breadcrumbs/breadcrumb.test.ts @@ -0,0 +1,72 @@ +import { fixture, html, expect, unsafeStatic } from "@open-wc/testing"; +import type { ZetaBreadcrumb } from "../../components/breadcrumbs/breadcrumb.js"; +import "../../components/breadcrumbs/breadcrumb.js"; +import { MouseActions } from "../utils.js"; + +describe("zeta-breadcrumb", () => { + let subject: ZetaBreadcrumb; + const maxItems = 4; + const rounded = true; + + const createComponent = ( + template = `<zeta-breadcrumb maxItems=${maxItems} .rounded=${rounded}> + <zeta-breadcrumb-item> Standard breadcrumb </zeta-breadcrumb-item> + <zeta-breadcrumb-item> Text with separator 1</zeta-breadcrumb-item> + <zeta-breadcrumb-item> Text with separator 2</zeta-breadcrumb-item> + <zeta-breadcrumb-item> Text with separator 3</zeta-breadcrumb-item> + <zeta-breadcrumb-item> Text with separator 4</zeta-breadcrumb-item> + <zeta-breadcrumb-item> + <zeta-icon slot="icon">star</zeta-icon> + Icon before with separator + </zeta-breadcrumb-item> + </zeta-breadcrumb>` + ) => { + // prettier-ignore + return fixture<ZetaBreadcrumb>(html`${unsafeStatic(template)}`); + }; + + beforeEach(async () => { + subject = await createComponent(); + }); + + describe("Accessibility Tests", () => { + it("meets accessibility requirements", async () => { + await expect(subject).shadowDom.to.be.accessible(); + }); + }); + + describe("Content Tests", () => { + it("renders the correct number of breadcrumb items", async () => { + const breadcrumbItems = subject.shadowRoot!.querySelectorAll("zeta-breadcrumb-item"); + + await expect(breadcrumbItems.length).to.equal(4); + }); + + it("renders the truncated breadcrumb ", () => { + const moreMenu = subject.shadowRoot!.querySelector(".more-menu"); + + expect(moreMenu).to.not.be.null; + }); + }); + + // describe("Dimensions Tests", () => {}); + + // describe("Styling Tests", () => {}); + + describe("Interaction Tests", () => { + it.skip("renders the correct number of breadcrumb items after the more menu has been clicked", async () => { + const moreMenu = subject.shadowRoot!.querySelector(".more-menu > button") as HTMLButtonElement; + await MouseActions.click(moreMenu); + + subject.requestUpdate(); + + const breadcrumbItems = subject.shadowRoot!.querySelectorAll("zeta-breadcrumb-item"); + + await expect(breadcrumbItems.length).to.equal(6); + }); + }); + + // describe("Golden Tests", () => {}); + + // describe("Performance Tests", () => {}); +}); diff --git a/src/test/button/button.test.ts b/src/test/button/button.test.ts new file mode 100644 index 0000000..4a60939 --- /dev/null +++ b/src/test/button/button.test.ts @@ -0,0 +1,109 @@ +import { fixture, html, expect, unsafeStatic } from "@open-wc/testing"; +import type { ZetaButton } from "../../components/button/button.js"; +import "../../components/button/button.js"; + +const buttonText = "Button"; + +describe("zeta-button", () => { + let subject: ZetaButton; + + const createComponent = (template = `<zeta-button>${buttonText}</zeta-button>`) => { + // prettier-ignore + return fixture<ZetaButton>(html`${unsafeStatic(template)}`); + }; + + beforeEach(async () => { + subject = await createComponent(); + }); + + describe("Accessibility Tests", () => { + it("meets accessibility requirements", async () => { + await expect(subject).shadowDom.to.be.accessible(); + }); + }); + + describe("Content Tests", () => { + it("should render the correct text within the button", async () => { + await expect(subject.lastChild?.nodeValue).to.equal(buttonText); + }); + }); + + // describe("Dimensions Tests", () => {}); + + // describe("Styling Tests", () => {}); + + // describe("Interaction Tests", () => {}); + + // describe("Golden Tests", () => {}); + + // describe("Performance Tests", () => {}); + + // flavors.map(flavor => + // describe(`zeta-button ${flavor}`, () => { + // // TODO test for correct colors + // it("meets accessibility requirements", async () => { + // await expect(subject).shadowDom.to.be.accessible(); + // }); + // }) + // ); +}); + +describe("zeta-button AS form reset control", () => { + const TEST_STRING = "test string"; + let button: ZetaButton; + let form: HTMLFormElement; + let input: HTMLInputElement; + + beforeEach(async () => { + form = await fixture( + html`<form> + <input type="text" name="text-control" /> + <zeta-button type="reset"></zeta-button> + </form>` + ); + input = form.querySelector("input[name='text-control']") as HTMLInputElement; + button = form.querySelector("zeta-button[type='reset']") as ZetaButton; + }); + + // describe("Accessibility Tests", () => {}); + + // describe("Content Tests", () => {}); + + // describe("Dimensions Tests", () => {}); + + // describe("Styling Tests", () => {}); + + describe("Interaction Tests", () => { + it("should reset forms", async () => { + // debugger; + expect(input?.value).to.be.empty; + input.value = TEST_STRING; + await expect(input?.value).to.equal(TEST_STRING); + button?.click(); + //console.log("loko at me", input?.value, form.querySelector("input[name='text-control']"), form.querySelector("input[name='text-control']").value) + await expect(input?.value).to.equal(""); + }); + + it("should not reset forms if disabled via JS", async () => { + expect(input?.value).to.be.empty; + input.value = TEST_STRING; + await expect(input?.value).to.equal(TEST_STRING); + button.disabled = true; + button?.click(); + await expect(input?.value).to.equal(TEST_STRING); + }); + + it("should not reset forms if disabled via DOM", async () => { + expect(input?.value).to.be.empty; + input.value = TEST_STRING; + await expect(input?.value).to.equal(TEST_STRING); + button.setAttribute("disabled", ""); + button?.click(); + await expect(input?.value).to.equal(TEST_STRING); + }); + }); + + // describe("Golden Tests", () => {}); + + // describe("Performance Tests", () => {}); +}); diff --git a/src/test/button/icon-button.test.ts b/src/test/button/icon-button.test.ts new file mode 100644 index 0000000..eba6de6 --- /dev/null +++ b/src/test/button/icon-button.test.ts @@ -0,0 +1,208 @@ +import { fixture, html, expect, elementUpdated } from "@open-wc/testing"; +import type { ZetaIconButton } from "../../components/button/icon-button/icon-button.js"; +import { getCssVarColorValue, toRGB, getSlotText } from "../utils.js"; +import "../../components/button/icon-button/icon-button.js"; +import "../../index.css"; + +const flavors = ["primary", "secondary", "positive", "negative", "outline", "outline-subtle", "text"]; +const iconName = "check"; + +describe("zeta-icon-button", () => { + describe("Accessibility Tests", () => { + flavors.map(flavor => { + if (flavor !== "secondary") { + // TODO: from designs, the secondary flavor does not meet accessability requirements + it(`meets accessibility requirements for the ${flavor} flavor`, async () => { + const iconButton: ZetaIconButton = await fixture(html`<zeta-icon-button>${iconName}</zeta-icon-button>`); + + iconButton.setAttribute("flavor", flavor); + iconButton.setAttribute("icon-name", iconName); + await elementUpdated(iconButton); + await expect(iconButton).shadowDom.to.be.accessible(); + }); + } + }); + }); + + describe("Content Tests", () => { + it("should render the correct icon", async () => { + const iconButton: ZetaIconButton = await fixture(html`<zeta-icon-button>${iconName}</zeta-icon-button>`); + + await expect(getSlotText(iconButton.shadowRoot!.querySelector("zeta-icon")!)).to.equal(iconName); + }); + }); + + // describe("Dimensions Tests", () => {}); + + describe("Styling Tests", () => { + it("should display correct icon color when disabled", async () => { + const iconButton: ZetaIconButton = await fixture(html`<zeta-icon-button>${iconName}</zeta-icon-button>`); + + iconButton.setAttribute("disabled", "true"); + await elementUpdated(iconButton); + + const icon: Element | null | undefined = iconButton.shadowRoot?.querySelector("zeta-icon"); + await expect(getComputedStyle(icon!).color).to.equal(getCssVarColorValue(icon!, "--main-disabled")); + }); + + it("should display correct icon color for primary flavor", async () => { + const iconButton: ZetaIconButton = await fixture(html`<zeta-icon-button>${iconName}</zeta-icon-button>`); + + iconButton.setAttribute("flavor", "primary"); + await elementUpdated(iconButton); + + const icon: Element | null | undefined = iconButton.shadowRoot?.querySelector("zeta-icon"); + await expect(getComputedStyle(icon!).color).to.equal(getCssVarColorValue(icon!, "--main-inverse")); + }); + + it("should display correct icon color for negative flavor", async () => { + const iconButton: ZetaIconButton = await fixture(html`<zeta-icon-button>${iconName}</zeta-icon-button>`); + + iconButton.setAttribute("flavor", "negative"); + await elementUpdated(iconButton); + + const icon: Element | null | undefined = iconButton.shadowRoot?.querySelector("zeta-icon"); + await expect(getComputedStyle(icon!).color).to.equal(getCssVarColorValue(icon!, "--main-inverse")); + }); + + it("should display correct icon color for text flavor", async () => { + const iconButton: ZetaIconButton = await fixture(html`<zeta-icon-button>${iconName}</zeta-icon-button>`); + + iconButton.setAttribute("flavor", "text"); + await elementUpdated(iconButton); + + const icon: Element | null | undefined = iconButton.shadowRoot?.querySelector("zeta-icon"); + await expect(getComputedStyle(icon!).color).to.equal(getCssVarColorValue(icon!, "--main-primary")); + }); + + it("should display correct icon color for outline-subtle flavor", async () => { + const iconButton: ZetaIconButton = await fixture(html`<zeta-icon-button>${iconName}</zeta-icon-button>`); + + iconButton.setAttribute("flavor", "outline-subtle"); + await elementUpdated(iconButton); + + const icon: Element | null | undefined = iconButton.shadowRoot?.querySelector("zeta-icon"); + await expect(getComputedStyle(icon!).color).to.equal(getCssVarColorValue(icon!, "--main-default")); + }); + + flavors.map(flavor => { + it(`button should have correct background color for the ${flavor} flavor`, async () => { + const iconButton: ZetaIconButton = await fixture(html`<zeta-icon-button>${iconName}</zeta-icon-button>`); + + iconButton.setAttribute("flavor", flavor); + const button: Element | null | undefined = iconButton.shadowRoot?.querySelector("button"); + const buttonColor = getComputedStyle(button!).backgroundColor; + let finalFlavor; + switch (flavor) { + case "outline": + case "outline-subtle": + case "text": + case "basic": + case "basic-negative": + finalFlavor = "--surface-default"; + break; + default: + finalFlavor = `--state-${flavor}-enabled`; + } + await expect(buttonColor).to.equal(getCssVarColorValue(button!, finalFlavor)); + }); + }); + + it("should render the button with the correct color when set by CSS Variable", async () => { + const iconButton: ZetaIconButton = await fixture(html`<zeta-icon-button>${iconName}</zeta-icon-button>`); + + const testColor = "#BADA55"; + iconButton.style.setProperty("--icon-button-color", testColor); + const button: Element | null | undefined = iconButton.shadowRoot?.querySelector("button"); + const buttonColor = getComputedStyle(button!).backgroundColor; + await expect(buttonColor).to.equal(toRGB(testColor)); + }); + + it("should render the icon with the correct color when set by CSS Variable", async () => { + const iconButton: ZetaIconButton = await fixture(html`<zeta-icon-button>${iconName}</zeta-icon-button>`); + + const testColor = "#BADA55"; + iconButton.style.setProperty("--icon-button-icon-color", testColor); + const icon: Element | null | undefined = iconButton.shadowRoot?.querySelector("zeta-icon"); + const iconColor = getComputedStyle(icon!).color; + await expect(iconColor).to.equal(toRGB(testColor)); + }); + + it("should render the icon with the correct color when disabled when set by CSS Variable", async () => { + const iconButton: ZetaIconButton = await fixture(html`<zeta-icon-button>${iconName}</zeta-icon-button>`); + + const testColor = "#BADA55"; + iconButton.style.setProperty("--icon-button-icon-color-disabled", testColor); + iconButton.setAttribute("disabled", "true"); + await elementUpdated(iconButton); + + const icon: Element | null | undefined = iconButton.shadowRoot?.querySelector("zeta-icon"); + const iconColor = getComputedStyle(icon!).color; + await expect(iconColor).to.equal(toRGB(testColor)); + }); + }); + + // describe("Interaction Tests", () => {}); + + // describe("Golden Tests", () => {}); + + // describe("Performance Tests", () => {}); +}); + +describe("zeta-icon-button AS form reset control", () => { + const TEST_STRING = "test string"; + let button: ZetaIconButton; + let form: HTMLFormElement; + let input: HTMLInputElement; + + beforeEach(async () => { + form = await fixture( + html`<form> + <input type="text" name="text-control" /> + <zeta-icon-button type="reset">reset</zeta-icon-button> + </form>` + ); + input = form.querySelector("input[name='text-control']") as HTMLInputElement; + button = form.querySelector("zeta-icon-button[type='reset']") as ZetaIconButton; + }); + + // describe("Accessibility Tests", () => {}); + + // describe("Content Tests", () => {}); + + // describe("Dimensions Tests", () => {}); + + // describe("Styling Tests", () => {}); + + describe("Interaction Tests", () => { + it("should reset forms", async () => { + expect(input?.value).to.be.empty; + input.value = TEST_STRING; + await expect(input?.value).to.equal(TEST_STRING); + button?.click(); + await expect(input?.value).to.equal(""); + }); + + it("should not reset forms if disabled via JS", async () => { + expect(input?.value).to.be.empty; + input.value = TEST_STRING; + await expect(input?.value).to.equal(TEST_STRING); + button.disabled = true; + button?.click(); + await expect(input?.value).to.equal(TEST_STRING); + }); + + it("should not reset forms if disabled via DOM", async () => { + expect(input?.value).to.be.empty; + input.value = TEST_STRING; + await expect(input?.value).to.equal(TEST_STRING); + button.setAttribute("disabled", ""); + button?.click(); + await expect(input?.value).to.equal(TEST_STRING); + }); + }); + + // describe("Golden Tests", () => {}); + + // describe("Performance Tests", () => {}); +}); diff --git a/src/test/card/card-header.test.ts b/src/test/card/card-header.test.ts new file mode 100644 index 0000000..4237b19 --- /dev/null +++ b/src/test/card/card-header.test.ts @@ -0,0 +1,73 @@ +import { fixture, html, unsafeStatic, expect } from "@open-wc/testing"; +import type { ZetaCardHeader } from "../../components/card/card-header/card-header.js"; +import "../../components/card/card-header/card-header.js"; +import "../../components/avatar/avatar.js"; +import "../../components/button/icon-button/icon-button.js"; + +describe("zeta-card-header", () => { + let subject: ZetaCardHeader; + + const headingText = "Headline"; + const subHeadingText = "Subhead"; + + const leadingId = "avatar"; + const trailingId = "iconButton"; + + const createComponent = ( + template = `<zeta-card-header headline=${headingText} subHeadline=${subHeadingText}> + <zeta-avatar slot="leading" id=${leadingId}></zeta-avatar> + <zeta-icon-button slot="trailing" id=${trailingId} flavor="basic">more_vertical</zeta-icon-button> + </zeta-card-header>` + ) => { + // prettier-ignore + return fixture<ZetaCardHeader>(html`${unsafeStatic(template)}`); + }; + + beforeEach(async () => { + subject = await createComponent(); + }); + + describe("Accessibility Tests", () => { + it("meets accessability requirements", async () => { + await expect(subject).shadowDom.to.be.accessible(); + }); + }); + + describe("Content Tests", () => { + it("sets the heading correctly", () => { + const headerElement = subject.shadowRoot?.querySelector("h1"); + + expect(headerElement).to.not.be.undefined; + void expect(headerElement?.textContent).to.equal(headingText); + }); + + it("sets the subheading correctly", async () => { + const subHeaderElement = subject.shadowRoot?.querySelector("h2"); + + expect(subHeaderElement).to.not.be.undefined; + await expect(subHeaderElement?.textContent).to.equal(subHeadingText); + }); + + it("sets the leading content correctly", () => { + const leadingElement = subject.shadowRoot?.querySelector(`#${leadingId}`); + + expect(leadingElement).to.not.be.undefined; + }); + + it("sets the trailing content correctly", () => { + const trailingElement = subject.shadowRoot?.querySelector(`#${trailingId}`); + + expect(trailingElement).to.not.be.undefined; + }); + }); + + // describe("Dimensions Tests", () => {}); + + // describe("Styling Tests", () => {}); + + // describe("Interaction Tests", () => {}); + + // describe("Golden Tests", () => {}); + + // describe("Performance Tests", () => {}); +}); diff --git a/src/test/checkbox/checkbox.test.ts b/src/test/checkbox/checkbox.test.ts new file mode 100644 index 0000000..5571fb7 --- /dev/null +++ b/src/test/checkbox/checkbox.test.ts @@ -0,0 +1,128 @@ +import { fixture, html, unsafeStatic, expect, elementUpdated, assert } from "@open-wc/testing"; +import { ZetaCheckbox } from "../../components/checkbox/checkbox.js"; +import "../../components/checkbox/checkbox.js"; + +describe("zeta-checkbox", () => { + let subject: ZetaCheckbox; + + const createComponent = (template = `<zeta-checkbox></zeta-checkbox>`) => { + // prettier-ignore + return fixture<ZetaCheckbox>(html`${unsafeStatic(template)}`); + }; + + beforeEach(async () => { + subject = await createComponent(); + }); + + /** TODO need a full reassess of form control labels */ + // describe("Accessibility Tests", () => { + // it.skip("meets accessibility requirements", async () => { + // const el = await fixture(html`<label for="checky">Label</label><zeta-checkbox name="checky"></zeta-checkbox>`); + // await expect(el).to.be.accessible(); + // }); + + // it.skip("doesn't meet accessibility requirement without a label", async () => { + // const el = await fixture(html`<zeta-checkbox></zeta-checkbox>`); + // await expect(el).not.to.be.accessible(); + // }); + + // it.skip("is accessible by adding aria-label", async () => { + // const el = await fixture(html`<zeta-checkbox aria-label="Test Checkbox"></zeta-checkbox>`); + // await expect(el).to.be.accessible(); + // }); + // }); + + describe("Content Tests", () => { + it("sets the name attribute correctly", async () => { + const name = "myCheckbox"; + subject.name = name; + await elementUpdated(subject); + + const inputElement = subject.shadowRoot?.querySelector("input"); + expect(inputElement).to.not.be.undefined; + await expect(inputElement?.getAttribute("name")).to.equal(name); + }); + + it("sets the id attribute correctly", async () => { + const id = "myCheckbox"; + subject.setAttribute("id", id); + await elementUpdated(subject); + + const inputElement = subject.shadowRoot?.querySelector("input"); + expect(inputElement).to.not.be.undefined; + await expect(inputElement?.getAttribute("id")).to.equal(id); + }); + + it("creates from document.createElement", function () { + const el = document.createElement("zeta-checkbox"); + assert.equal("ZETA-CHECKBOX", el.nodeName); + }); + + it("creates from constructor", function () { + const el = new ZetaCheckbox(); + assert.equal("ZETA-CHECKBOX", el.nodeName); + }); + }); + + // describe("Dimensions Tests", () => {}); + + // describe("Styling Tests", () => {}); + + describe("Interaction Tests", () => { + it("changes the checked state when clicked", async () => { + subject.click(); + await expect(subject.checked).to.equal(true); + }); + }); + + // describe("Golden Tests", () => {}); + + // describe("Performance Tests", () => {}); +}); + +describe("zeta-checkbox + label", () => { + let subject: ZetaCheckbox; + const labelTest = "Test Label"; + + const createComponent = (template = `<zeta-checkbox id="checkbox">${labelTest}</zeta-checkbox>`) => { + // prettier-ignore + return fixture<ZetaCheckbox>(html`${unsafeStatic(template)}`); + }; + + beforeEach(async () => { + subject = await createComponent(); + }); + + // describe("Accessibility Tests", () => {}); + + describe("Content Tests", () => { + it("renders the checkbox label correctly", async () => { + const slot = subject.shadowRoot?.querySelector("slot"); + await expect( + slot + ?.assignedNodes() + .map(a => a.nodeValue) + .join() + ).to.equal(labelTest); + }); + }); + + // describe("Dimensions Tests", () => {}); + + // describe("Styling Tests", () => {}); + + describe("Interaction Tests", () => { + it("checks the checkbox when label is clicked", async () => { + const labelText = subject.shadowRoot?.querySelector("label"); + expect(subject.getAttribute("checked")).to.be.null; + labelText?.click(); + await elementUpdated(subject); + await expect(subject.getAttribute("checked")).to.be.equal(""); + expect(subject.checked).to.be.true; + }); + }); + + // describe("Golden Tests", () => {}); + + // describe("Performance Tests", () => {}); +}); diff --git a/src/test/chips/assist-chip/assist-chip.test.ts b/src/test/chips/assist-chip/assist-chip.test.ts new file mode 100644 index 0000000..a5b0977 --- /dev/null +++ b/src/test/chips/assist-chip/assist-chip.test.ts @@ -0,0 +1,46 @@ +import { fixture, html, expect, unsafeStatic } from "@open-wc/testing"; +import type { ZetaAssistChip } from "../../../index.js"; +import "../../../index.js"; + +const labelText = "Label"; +const icon = "star"; + +describe("zeta-assist-chip", () => { + let subject: ZetaAssistChip; + + const createComponent = (template = `<zeta-assist-chip icon=${icon}>${labelText}</zeta-assist-chip>`) => { + // prettier-ignore + return fixture<ZetaAssistChip>(html`${unsafeStatic(template)}`); + }; + + beforeEach(async () => { + subject = await createComponent(); + }); + + describe("Accessibility Tests", () => { + it("meets accessibility requirements", async () => { + await expect(subject).shadowDom.to.be.accessible(); + }); + }); + + describe("Content Tests", () => { + it("sets the correct text on the chip", async () => { + await expect(subject.lastChild?.nodeValue).to.equal(labelText); + }); + + it("displays the correct icon", async () => { + const iconElement = subject.shadowRoot?.querySelector("zeta-icon"); + await expect(iconElement?.textContent).to.equal(icon); + }); + }); + + // describe("Dimensions Tests", () => {}); + + // describe("Styling Tests", () => {}); + + // describe("Interaction Tests", () => {}); + + // describe("Golden Tests", () => {}); + + // describe("Performance Tests", () => {}); +}); diff --git a/src/test/chips/filter-chip/filter-chip.test.ts b/src/test/chips/filter-chip/filter-chip.test.ts new file mode 100644 index 0000000..47f0253 --- /dev/null +++ b/src/test/chips/filter-chip/filter-chip.test.ts @@ -0,0 +1,50 @@ +import { fixture, html, expect, unsafeStatic, elementUpdated } from "@open-wc/testing"; +import type { ZetaFilterChip } from "../../../index.js"; +import "../../../index.js"; + +const labelText = "Label"; + +describe("zeta-filter-chip", () => { + let subject: ZetaFilterChip; + + const createComponent = (template = `<zeta-filter-chip>${labelText}</zeta-filter-chip>`) => { + // prettier-ignore + return fixture<ZetaFilterChip>(html`${unsafeStatic(template)}`); + }; + + beforeEach(async () => { + subject = await createComponent(); + }); + + describe("Accessibility Tests", () => { + it("it meets accessibility requirements", async () => { + await expect(subject).shadowDom.to.be.accessible(); + }); + }); + + describe("Content Tests", () => { + it("sets the correct text on the chip", async () => { + await expect(subject.lastChild?.nodeValue).to.equal(labelText); + }); + + it("shows the check icon when active", async () => { + subject.active = true; + await elementUpdated(subject); + + const iconElement = subject.shadowRoot?.querySelector("zeta-icon"); + + expect(iconElement).to.exist; + await expect(iconElement?.textContent).to.equal("check_mark"); + }); + }); + + // describe("Dimensions Tests", () => {}); + + // describe("Styling Tests", () => {}); + + // describe("Interaction Tests", () => {}); + + // describe("Golden Tests", () => {}); + + // describe("Performance Tests", () => {}); +}); diff --git a/src/test/chips/input-chip/input-chip.test.ts b/src/test/chips/input-chip/input-chip.test.ts new file mode 100644 index 0000000..6d4a82f --- /dev/null +++ b/src/test/chips/input-chip/input-chip.test.ts @@ -0,0 +1,40 @@ +import { fixture, html, expect, unsafeStatic } from "@open-wc/testing"; +import type { ZetaInputChip } from "../../../index.js"; +import "../../../index.js"; + +const labelText = "Label"; + +describe("zeta-input-chip", () => { + let subject: ZetaInputChip; + + const createComponent = (template = `<zeta-input-chip>${labelText}</zeta-input-chip>`) => { + // prettier-ignore + return fixture<ZetaInputChip>(html`${unsafeStatic(template)}`); + }; + + beforeEach(async () => { + subject = await createComponent(); + }); + + describe("Accessibility Tests", () => { + it("it meets accessibility requirements", async () => { + await expect(subject).shadowDom.to.be.accessible(); + }); + }); + + describe("Content Tests", () => { + it("sets the correct text on the chip", async () => { + await expect(subject.lastChild?.nodeValue).to.equal(labelText); + }); + }); + + // describe("Dimensions Tests", () => {}); + + // describe("Styling Tests", () => {}); + + // describe("Interaction Tests", () => {}); + + // describe("Golden Tests", () => {}); + + // describe("Performance Tests", () => {}); +}); diff --git a/src/test/dialog/dialog.test.ts b/src/test/dialog/dialog.test.ts new file mode 100644 index 0000000..6c63755 --- /dev/null +++ b/src/test/dialog/dialog.test.ts @@ -0,0 +1,53 @@ +import { assert, fixture, html } from "@open-wc/testing"; +import { ZetaDialog } from "../../components/dialog/dialog.js"; +import "../../components/dialog/dialog.js"; + +describe("zeta-dialog", () => { + // describe("Accessibility Tests", () => {}); + + describe("Content Tests", () => { + it("creates from document.createElement", function () { + const el = document.createElement("zeta-dialog"); + assert.equal("ZETA-DIALOG", el.nodeName); + }); + + it("creates from constructor", function () { + const el = new ZetaDialog(); + assert.equal("ZETA-DIALOG", el.nodeName); + }); + + it("changes open property on open", async () => { + // prettier-ignore + const el = await fixture<ZetaDialog>(html` <zeta-dialog> </zeta-dialog> `); + await el.updateComplete; + await el.show(); + assert.equal(el.open, true); + }); + + it("changes open property on close", async () => { + // prettier-ignore + const el = await fixture<ZetaDialog>(html` <zeta-dialog> </zeta-dialog> `); + await el.show(); + await el.hide(); + assert.equal(el.open, false); + }); + + it("calls close method and sets return value", async () => { + // prettier-ignore + const el = await fixture<ZetaDialog>(html` <zeta-dialog> </zeta-dialog> `); + await el.show(); + await el.hide("testing"); + assert.equal(el.returnValue, "testing"); + }); + }); + + // describe("Dimensions Tests", () => {}); + + // describe("Styling Tests", () => {}); + + // describe("Interaction Tests", () => {}); + + // describe("Golden Tests", () => {}); + + // describe("Performance Tests", () => {}); +}); diff --git a/src/test/dialog/dialog.test.ts.broken b/src/test/dialog/dialog.test.ts.broken new file mode 100644 index 0000000..b5b61b0 --- /dev/null +++ b/src/test/dialog/dialog.test.ts.broken @@ -0,0 +1,37 @@ +import { assert, fixture } from "@open-wc/testing"; +import { ZetaDialog } from "../../src"; +import "../../src/components/dialog/dialog.js"; +import { html } from "lit"; + +describe("zeta-dialog", () => { + it("creates from document.createElement", function () { + const el = document.createElement("zeta-dialog"); + assert.equal("ZETA-DIALOG", el.nodeName); + }); + + it("creates from constructor", function () { + const el = new ZetaDialog(); + assert.equal("ZETA-DIALOG", el.nodeName); + }); + + it("changes open property on open", async () => { + const el = await fixture<ZetaDialog>(html` <zeta-dialog> </zeta-dialog> `); + await el.updateComplete; + await el.show(); + assert.equal(el.open, true); + }); + + it("changes open property on close", async () => { + const el = await fixture<ZetaDialog>(html` <zeta-dialog> </zeta-dialog> `); + await el.show(); + await el.hide(); + assert.equal(el.open, false); + }); + + it("calls close method and sets return value", async () => { + const el = await fixture<ZetaDialog>(html` <zeta-dialog> </zeta-dialog> `); + await el.show(); + await el.hide("testing"); + assert.equal(el.returnValue, "testing"); + }); +}); diff --git a/src/test/dropdown/__snapshots__/dropdown-menu-button.test.snap.js b/src/test/dropdown/__snapshots__/dropdown-menu-button.test.snap.js new file mode 100644 index 0000000..c4dc7c4 --- /dev/null +++ b/src/test/dropdown/__snapshots__/dropdown-menu-button.test.snap.js @@ -0,0 +1,101 @@ +/* @web/test-runner snapshot v1 */ +export const snapshots = {}; + +snapshots["zeta-dropdown-menu-button renders the dropdown menu button correctly"] = `<zeta-button + flavor="primary" + id="anchor" + rounded="" + size="medium" +> + <slot> + </slot> + <zeta-icon rounded=""> + chevron_left + </zeta-icon> +</zeta-button> +<zeta-droppable + rounded="" + style="top: 48px; width: 145.406px;" +> + <zeta-dropdown-menu-item rounded=""> + Auto Item + </zeta-dropdown-menu-item> +</zeta-droppable> +<input + aria-disabled="false" + aria-required="false" + hidden="" + name="dropdown-menu" + placeholder="" + type="text-dropdown" +> +`; +/* end snapshot zeta-dropdown-menu-button renders the dropdown menu button correctly */ + +snapshots["zeta-dropdown-menu-button renders the dropdown menu items correctly"] = `<zeta-button + flavor="primary" + id="anchor" + rounded="" + size="medium" +> + <slot> + </slot> + <zeta-icon rounded=""> + expand_more + </zeta-icon> +</zeta-button> +<zeta-droppable + open="" + rounded="" + style="top: 48px; width: 145.406px; left: 8px;" +> + <zeta-dropdown-menu-item rounded=""> + Auto Item + </zeta-dropdown-menu-item> +</zeta-droppable> +<input + aria-disabled="false" + aria-required="false" + hidden="" + name="dropdown-menu" + placeholder="" + type="text-dropdown" +> +`; +/* end snapshot zeta-dropdown-menu-button renders the dropdown menu items correctly */ +snapshots["zeta-dropdown-menu-button Golden Tests renders the dropdown menu button correctly"] = `<zeta-button + flavor="primary" + id="anchor" + rounded="" + size="medium" +> + <slot> + </slot> + <zeta-icon rounded=""> + chevron_left + </zeta-icon> +</zeta-button> +<zeta-droppable + rounded="" + style="top: 168px; width: calc(173.406px);" +> + <zeta-dropdown-menu-item rounded=""> + <zeta-icon + rounded="" + slot="icon" + > + star + </zeta-icon> + Auto Item + </zeta-dropdown-menu-item> +</zeta-droppable> +<input + aria-disabled="false" + aria-required="false" + hidden="" + name="dropdown-menu" + placeholder="" + type="text-dropdown" +> +`; +/* end snapshot zeta-dropdown-menu-button Golden Tests renders the dropdown menu button correctly */ diff --git a/src/test/dropdown/dropdown-menu-button.test.ts b/src/test/dropdown/dropdown-menu-button.test.ts new file mode 100644 index 0000000..637650f --- /dev/null +++ b/src/test/dropdown/dropdown-menu-button.test.ts @@ -0,0 +1,215 @@ +import { fixture, html, expect, unsafeStatic } from "@open-wc/testing"; +import type { ZetaButton } from "../../components/button/button.js"; +import type { ZetaCheckbox } from "../../components/checkbox/checkbox.js"; +import type { ZetaDropdownMenuButton } from "../../components/dropdown/dropdown-menu/dropdown-menu-button.js"; +import type { ZetaDropdownMenuItem } from "../../components/dropdown/menu-item/dropdown-menu-item.js"; +import type { ZetaRadioButton } from "../../components/radio-button/radio-button.js"; +import "../../components/dropdown/dropdown-menu/dropdown-menu-button.js"; +import type { ZetaDropdownItem } from "../../components/dropdown/dropdown-menu/dropdown-menu-button.js"; +// import { getIconName } from "../utils.js"; + +describe("zeta-dropdown-menu-button", () => { + let subject: ZetaDropdownMenuButton; + + const createComponent = ( + template = ` <zeta-dropdown-menu-button + size="medium" + name="dropdown-menu" + rounded=true + flavor="primary" + type="text-dropdown" + + > + Dropdown Menu + </zeta-dropdown-menu-button>` + ) => { + // prettier-ignore + return fixture<ZetaDropdownMenuButton>(html`${unsafeStatic(template)}`); + }; + + beforeEach(async () => { + subject = await createComponent(); + }); + + describe("Accessibility Tests", () => { + // TODO: Colors currently fail accessability + it.skip("meets accessability requirements", async () => { + await expect(subject).shadowDom.to.be.accessible(); + }); + }); + + describe("Content Tests", () => { + it("renders the text correctly", async () => { + const text = "Dropdown Menu"; + await expect(subject.innerText.trim()).to.equal(text); + }); + + // it.skip("renders an icon with the correct name", async () => { + // // fails due to slotting icon changes + // const iconName = "chevron_left"; + + // const iconElement = subject.shadowRoot?.querySelector("zeta-icon") as ZetaIcon; + + // expect(iconElement).to.not.be.undefined; + // await expect(getIconName(iconElement)).to.equal(iconName); + // }); + + // it.skip("renders an icon with the correct name when open", async () => { + // // fails due to slotting icon changes + // const iconName = "expand_more"; + + // const iconElement = subject.shadowRoot?.querySelector("zeta-icon") as ZetaIcon; + + // (subject.shadowRoot?.querySelector("zeta-button") as ZetaButton).click(); + // await subject.updateComplete; + + // expect(iconElement).to.not.be.undefined; + // await expect(getIconName(iconElement)).to.equal(iconName); + // }); + }); + + describe("Dimensions Tests", () => { + it("has the same width as the zeta droppable", async () => { + (subject.shadowRoot?.querySelector("zeta-button") as ZetaButton).click(); + await subject.updateComplete; + + const dropdownMenuButtonWidth = subject.getBoundingClientRect().width; + const droppableWidth = subject.droppable.getBoundingClientRect().width; + + await expect(dropdownMenuButtonWidth).to.equal(droppableWidth); + }); + + it("aligns the zeta droppable to the bottom of the dropdown menu button", async () => { + (subject.shadowRoot?.querySelector("zeta-button") as ZetaButton).click(); + await subject.updateComplete; + + const dropdownMenuButtonRect = subject.getBoundingClientRect(); + const droppableRect = subject.droppable.getBoundingClientRect(); + + const dropdownMenuButtonBottom = dropdownMenuButtonRect.bottom; + const droppableTop = droppableRect.top; + + await expect(droppableTop).to.equal(dropdownMenuButtonBottom); + }); + }); + + // describe("Styling Tests", () => {}); + + describe("Interaction Tests", () => { + it("opens the dropdown menu when clicked", async () => { + (subject.shadowRoot?.querySelector("zeta-button") as ZetaButton).click(); + await subject.updateComplete; + + expect(subject.open).to.be.true; + }); + + it("closes the dropdown menu when clicked outside", async () => { + (subject.shadowRoot?.querySelector("zeta-button") as ZetaButton).click(); + await subject.updateComplete; + + document.body.click(); + await subject.updateComplete; + + expect(subject.open).to.be.false; + }); + + it("closes the dropdown menu when clicked inside", async () => { + (subject.shadowRoot?.querySelector("zeta-button") as ZetaButton).click(); + await subject.updateComplete; + + (subject.shadowRoot?.querySelector("zeta-dropdown-menu-item") as HTMLElement).click(); + await subject.updateComplete; + + expect(subject.open).to.be.false; + }); + + it("returns the selected item when a wrapping form is submitted", async () => { + subject.items = [ + { label: "Item 1", icon: "star", checked: false }, + { label: "Item 2", icon: "star", checked: false }, + { label: "Item 3", icon: "star", checked: false } + ]; + + (subject.shadowRoot?.querySelector("zeta-button") as ZetaButton).click(); + await subject.updateComplete; + + const dropdownMenuItem = subject.shadowRoot?.querySelectorAll("zeta-dropdown-menu-item") as NodeListOf<ZetaDropdownMenuItem>; + dropdownMenuItem[0].click(); + await subject.updateComplete; + + const form = document.createElement("form"); + form.appendChild(subject); + document.body.appendChild(form); + + const event = new Event("submit"); + form.dispatchEvent(event); + + const data = new FormData(event.target as HTMLFormElement); + + await expect(Object.fromEntries(data)["dropdown-menu"]).to.equal(subject.items[0].label); + }); + + it("returns the selected checkboxes when a wrapping form is submitted", async () => { + const selectedItem1: ZetaDropdownItem = { label: "Item 1", icon: "star", checked: false }; + const selectedItem2: ZetaDropdownItem = { label: "Item 2", icon: "star", checked: false }; + const selectedItem3: ZetaDropdownItem = { label: "Item 3", icon: "star", checked: false }; + subject.items = [selectedItem1, selectedItem2, selectedItem3]; + subject.type = "checkbox-dropdown"; + + (subject.shadowRoot?.querySelector("zeta-button") as ZetaButton).click(); + await subject.updateComplete; + + const checkboxElements = subject.shadowRoot?.querySelectorAll("zeta-checkbox") as NodeListOf<ZetaCheckbox>; + checkboxElements[0].click(); + checkboxElements[2].click(); + await subject.updateComplete; + + const form = document.createElement("form"); + form.appendChild(subject); + document.body.appendChild(form); + + const event = new Event("submit"); + form.dispatchEvent(event); + + const data = new FormData(event.target as HTMLFormElement); + const selectedCheckboxes = Array.from((Object.fromEntries(data)["dropdown-menu"] as string).split(",")); + + expect(selectedCheckboxes).to.have.lengthOf(2); + expect(selectedCheckboxes).to.include.members([selectedItem1.label, selectedItem3.label]); + }); + + it("returns the selected radio button when a wrapping form is submitted", async () => { + const selectedItem1: ZetaDropdownItem = { label: "Item 1", icon: "star", checked: false }; + const selectedItem2: ZetaDropdownItem = { label: "Item 2", icon: "star", checked: false }; + const selectedItem3: ZetaDropdownItem = { label: "Item 3", icon: "star", checked: false }; + subject.items = [selectedItem1, selectedItem2, selectedItem3]; + subject.type = "radio-dropdown"; + + (subject.shadowRoot?.querySelector("zeta-button") as ZetaButton).click(); + await subject.updateComplete; + + const radioButtonElements = subject.shadowRoot?.querySelectorAll("zeta-radio-button") as NodeListOf<ZetaRadioButton>; + radioButtonElements[0].shadowRoot?.querySelector("label")!.click(); + await subject.updateComplete; + + const form = document.createElement("form"); + form.appendChild(subject); + document.body.appendChild(form); + + const event = new Event("submit"); + form.dispatchEvent(event); + + const data = new FormData(event.target as HTMLFormElement); + + await expect(Object.fromEntries(data)["dropdown-menu"]).to.equal(selectedItem1.label); + }); + }); + + // describe("Golden Tests", () => { + // it("renders the dropdown menu button correctly", () => { + // expect(subject).shadowDom.to.equalSnapshot(); + // }); + // }); + + // describe("Performance Tests", () => {}); +}); diff --git a/src/test/dropdown/dropdown-menu-item.test.ts b/src/test/dropdown/dropdown-menu-item.test.ts new file mode 100644 index 0000000..59946d2 --- /dev/null +++ b/src/test/dropdown/dropdown-menu-item.test.ts @@ -0,0 +1,147 @@ +import { fixture, html, unsafeStatic, expect, elementUpdated } from "@open-wc/testing"; +import type { ZetaDropdownMenuItem } from "../../components/dropdown/menu-item/dropdown-menu-item.js"; +import type { ZetaIcon } from "../../components/icon/icon.js"; +import "../../components/dropdown/menu-item/dropdown-menu-item.js"; +import "../../index.css"; +import { getCssVarValue, getCssVarColorValue, getSlottedIconName } from "../utils.js"; +// import sinon from "sinon"; +import "../../components/dropdown/menu-item/dropdown-menu-item.js"; + +describe("zeta-dropdown-menu-item", () => { + const text = "Menu Item"; + + let subject: ZetaDropdownMenuItem; + + const createComponent = ( + template = `<zeta-dropdown-menu-item size="medium" rounded=true disabled=false> + <zeta-icon slot="icon">star</zeta-icon>${text}</zeta-dropdown-menu-item>` + ) => { + // prettier-ignore + return fixture<ZetaDropdownMenuItem> (html`${unsafeStatic(template)}`); + }; + + beforeEach(async () => { + subject = await createComponent(); + }); + + describe("Accessibility Tests", () => { + it("meets accessability requirements", async () => { + const item: ZetaDropdownMenuItem = await fixture(html`<zeta-dropdown-menu-item><zeta-icon slot="icon">star</zeta-icon>${text}</zeta-dropdown-menu-item>`); + await elementUpdated(item); + + await expect(item).shadowDom.to.be.accessible(); + }); + }); + + describe("Content Tests", () => { + it("renders the given text", async () => { + await expect(subject.lastChild?.nodeValue).to.equal(text); + }); + + it("renders an icon with the correct name with the 'default' type", async () => { + const icon = await fixture(html`<zeta-icon slot="icon">star</zeta-icon>`); + subject.appendChild(icon); + await elementUpdated(subject); + + await expect(getSlottedIconName(subject)).to.equal("star"); + }); + + it("sets the rounded attribute correctly", async () => { + const rounded = true; + subject.rounded = rounded; + await elementUpdated(subject); + + await expect(subject.rounded).to.equal(rounded); + }); + + it("sets the disabled attribute correctly", async () => { + const disabled = true; + subject.disabled = disabled; + await elementUpdated(subject); + + await expect(subject.disabled).to.equal(disabled); + }); + }); + + // describe("Dimensions Tests", () => {}); + + describe("Styling Tests", () => { + it("sets the correct colors when not disabled", async () => { + subject.disabled = false; + await elementUpdated(subject); + + const droppableItemElement = subject.shadowRoot?.querySelector(".droppable-item") as HTMLElement; + const style = window.getComputedStyle(droppableItemElement); + + await expect(style.color).to.equal(getCssVarColorValue(droppableItemElement, "--main-default")); + await expect(style.backgroundColor).to.equal(getCssVarColorValue(droppableItemElement, "--surface-default")); + }); + + it("sets the correct colors when disabled", async () => { + subject.disabled = true; + await elementUpdated(subject); + + const droppableItemElement = subject.shadowRoot?.querySelector(".droppable-item") as HTMLElement; + const style = window.getComputedStyle(droppableItemElement); + + await expect(style.color).to.equal(getCssVarColorValue(droppableItemElement, "--main-disabled")); + await expect(style.backgroundColor).to.equal(getCssVarColorValue(droppableItemElement, "--surface-disabled")); + }); + + it.skip("sets the correct icon color when not disabled", async () => { + // fails due to change to icon slot + subject.disabled = false; + await elementUpdated(subject); + + const iconElement = subject.shadowRoot?.querySelector("zeta-icon") as Element; + const style = window.getComputedStyle(iconElement); + + await expect(style.color).to.equal(getCssVarColorValue(iconElement, "--main-subtle")); + }); + + it.skip("sets the correct icon color when disabled", async () => { + // fails due to change to icon slot + subject.disabled = true; + await elementUpdated(subject); + + const iconElement = subject.shadowRoot?.querySelector("zeta-icon") as ZetaIcon; + const style = window.getComputedStyle(iconElement as Element); + + await expect(style.color).to.equal(getCssVarColorValue(iconElement, "--main-disabled")); + }); + + it("sets the correct border radius when rounded is true", async () => { + subject.rounded = true; + await elementUpdated(subject); + + const style = window.getComputedStyle(subject.shadowRoot?.querySelector(".droppable-item") as Element); + + await expect(style.borderRadius).to.equal(getCssVarValue(subject, "--radius-minimal")); + }); + + it("sets the correct border radius when rounded is false", async () => { + subject.rounded = false; + await elementUpdated(subject); + + const style = window.getComputedStyle(subject.shadowRoot?.querySelector(".droppable-item") as HTMLElement); + + await expect(style.borderRadius).to.equal(getCssVarValue(subject, "--radius-none")); + }); + }); + + // describe("Interaction Tests", () => { + // it("triggers a click event on keyboard down", () => { + // const clickSpy = sinon.spy(); + // subject.addEventListener("keydown", clickSpy); + + // const event = new KeyboardEvent("keydown", { key: "Enter" }); + // subject.dispatchEvent(event); + + // expect(clickSpy.calledOnce).to.be.true; + // }); + // }); + + // describe("Golden Tests", () => {}); + + // describe("Performance Tests", () => {}); +}); diff --git a/src/test/dropdown/droppable.test.ts b/src/test/dropdown/droppable.test.ts new file mode 100644 index 0000000..d0d95f2 --- /dev/null +++ b/src/test/dropdown/droppable.test.ts @@ -0,0 +1,97 @@ +import { fixture, html, unsafeStatic, expect, elementUpdated } from "@open-wc/testing"; +import type { ZetaDroppable } from "../../components/dropdown/droppable.js"; +import "../../components/dropdown/droppable.js"; +import "../../index.css"; + +describe("zeta-droppable", () => { + let subject: ZetaDroppable; + + const createComponent = ( + template = `<zeta-droppable rounded=true ?open="true"> + + </zeta-droppable>` + ) => { + // prettier-ignore + return fixture<ZetaDroppable>(html`${unsafeStatic(template)}`); + }; + + beforeEach(async () => { + subject = await createComponent(); + }); + + describe("Accessibility Tests", () => { + it("meets accessability requirements", async () => { + await expect(subject).shadowDom.to.be.accessible(); + }); + }); + + describe("Content Tests", () => { + it("sets the open attribute correctly", async () => { + let open = true; + subject.open = open; + await elementUpdated(subject); + + await expect(subject.open).to.equal(open); + + open = false; + subject.open = open; + await elementUpdated(subject); + + await expect(subject.open).to.equal(open); + }); + + it("is visible when open is true", async () => { + subject.open = true; + await elementUpdated(subject); + + const style = getComputedStyle(subject); + + await expect(style.display).to.not.equal("none"); + }); + + it("is not visible when open is false", async () => { + subject.open = false; + await elementUpdated(subject); + + const style = getComputedStyle(subject); + + await expect(style.display).to.equal("none"); + }); + + it("sets the rounded attribute correctly", async () => { + const rounded = true; + subject.rounded = rounded; + await elementUpdated(subject); + + await expect(subject.rounded).to.equal(rounded); + }); + }); + + // describe("Dimensions Tests", () => {}); + + describe("Styling Tests", () => { + it("sets the correct border radius when rounded is true", async () => { + subject.rounded = true; + await elementUpdated(subject); + + const style = getComputedStyle(subject); + + await expect(style.borderRadius).to.equal("4px"); + }); + + it("sets the correct border radius when rounded is false", async () => { + subject.rounded = false; + await elementUpdated(subject); + + const style = getComputedStyle(subject); + + await expect(style.borderRadius).to.equal("0px"); + }); + }); + + // describe("Interaction Tests", () => {}); + + // describe("Golden Tests", () => {}); + + // describe("Performance Tests", () => {}); +}); diff --git a/src/test/fab/fab.test.ts b/src/test/fab/fab.test.ts new file mode 100644 index 0000000..fdc11b1 --- /dev/null +++ b/src/test/fab/fab.test.ts @@ -0,0 +1,138 @@ +import { expect, fixture, html, unsafeStatic } from "@open-wc/testing"; +import type { ZetaFab } from "../../components/fab/fab.js"; +import { getCssVarValue } from "../utils.js"; +import "../../components/fab/fab.js"; +import "../../index.css"; + +describe("zeta-fab", () => { + const label = "Label"; + const iconInSlot = "star"; + + let subject: ZetaFab; + + const createComponent = ( + template = `<zeta-fab label=${label}> + ${iconInSlot} + </zeta-fab>` + ) => { + // prettier-ignore + return fixture<ZetaFab>(html`${unsafeStatic(template)}`); + }; + + beforeEach(async () => { + subject = await createComponent(); + }); + + describe("Accessibility Tests", () => { + it("meets accessability requirements", async () => { + await expect(subject).shadowDom.to.be.accessible(); + }); + }); + + describe("Content Tests", () => { + it("renders a button with a label", () => { + expect(subject.shadowRoot?.querySelector("button")).to.exist; + expect(subject.shadowRoot?.querySelector(".label")).to.exist; + }); + + it("renders a button with an icon", () => { + expect(subject.shadowRoot?.querySelector("zeta-icon")).to.exist; + }); + + it("renders no label when label is empty", async () => { + subject.label = ""; + await subject.updateComplete; + expect(subject.shadowRoot?.querySelector(".label")).to.not.exist; + }); + + it("renders an extended fab", async () => { + subject.extended = true; + await subject.updateComplete; + const labelExtentedFab = subject.shadowRoot?.querySelector("button .label"); + const labelDefault = subject.shadowRoot?.querySelector(":host > .label"); + await expect(subject.extended).to.equal(true); + expect(labelExtentedFab).to.exist; + expect(labelDefault).to.not.exist; + }); + + it("renders a default fab", async () => { + const labelExtentedFab = subject.shadowRoot?.querySelector("button .label"); + const labelDefault = subject.shadowRoot?.querySelector(":host > .label"); + await expect(subject.extended).to.equal(false); + expect(labelExtentedFab).to.not.exist; + expect(labelDefault).to.exist; + }); + }); + + describe("Dimensions Tests", () => { + it("renders a small fab", async () => { + const el = subject.shadowRoot?.querySelector("button"); + const height = getComputedStyle(el!).height; + const expectedHeight = parseInt(getCssVarValue(el!, "--spacing-medium")) * 2 + 24 + "px"; + await expect(subject.size).to.equal("small"); + await expect(height).to.equal(expectedHeight); + }); + + it("renders a large fab", async () => { + const fab: ZetaFab = await fixture(html`<zeta-fab size="large"></zeta-fab>`); + + const el = fab.shadowRoot?.querySelector("button"); + const height = getComputedStyle(el!).height; + const expectedHeight = parseInt(getCssVarValue(el!, "--spacing-xl")) * 2 + 36 + "px"; + await expect(height).to.equal(expectedHeight); + }); + }); + + describe("Styling Tests", () => { + it("renders a full rounded fab", async () => { + const el = subject.shadowRoot?.querySelector("button"); + const borderRadius = getComputedStyle(el!).borderRadius; + const expectedBorderRadius = getCssVarValue(el!, "--radius-full"); + await expect(subject.round).to.equal("full"); + await expect(borderRadius).to.equal(expectedBorderRadius); + }); + + it("renders a rounded small fab", async () => { + const fab: ZetaFab = await fixture(html`<zeta-fab size="small"></zeta-fab>`); + + const el = fab.shadowRoot?.querySelector("button"); + fab.round = true; + await fab.updateComplete; + const borderRadius = getComputedStyle(el!).borderRadius; + const expectedBorderRadius = getCssVarValue(el!, "--radius-rounded"); + await expect(fab.round).to.equal(true); + await expect(borderRadius).to.equal(expectedBorderRadius); + }); + + it("renders a rounded large fab", async () => { + const fab: ZetaFab = await fixture(html`<zeta-fab size="large"></zeta-fab>`); + + const el = fab.shadowRoot?.querySelector("button"); + fab.round = true; + await fab.updateComplete; + const borderRadius = getComputedStyle(el!).borderRadius; + const expectedBorderRadius = getCssVarValue(el!, "--radius-large"); + await expect(fab.round).to.equal(true); + await expect(fab.size).to.equal("large"); + await expect(borderRadius).to.equal(expectedBorderRadius); + }); + + it("renders a non-rounded fab", async () => { + const fab: ZetaFab = await fixture(html`<zeta-fab size="large"></zeta-fab>`); + + const el = fab.shadowRoot?.querySelector("button"); + fab.round = false; + await fab.updateComplete; + const borderRadius = getComputedStyle(el!).borderRadius; + const expectedBorderRadius = getCssVarValue(el!, "--radius-none"); + await expect(fab.round).to.equal(false); + await expect(borderRadius).to.equal(expectedBorderRadius); + }); + }); + + // describe("Interaction Tests", () => {}); + + // describe("Golden Tests", () => {}); + + // describe("Performance Tests", () => {}); +}); diff --git a/src/test/file-upload/file-upload.test.ts b/src/test/file-upload/file-upload.test.ts new file mode 100644 index 0000000..5bbcff8 --- /dev/null +++ b/src/test/file-upload/file-upload.test.ts @@ -0,0 +1,34 @@ +import { fixture, html, unsafeStatic, expect } from "@open-wc/testing"; +import type { ZetaFileUpload } from "../../components/file-upload/file-upload.js"; +import "../../components/file-upload/file-upload.js"; + +describe("zeta-file-upload", () => { + let subject: ZetaFileUpload; + + const createComponent = (template = `<zeta-file-upload></zeta-file-upload>`) => { + // prettier-ignore + return fixture<ZetaFileUpload>(html`${unsafeStatic(template)}`); + }; + + beforeEach(async () => { + subject = await createComponent(); + }); + + describe("Accessibility Tests", () => { + it("meets accessibility requirements", async () => { + await expect(subject).shadowDom.to.be.accessible(); + }); + }); + + // describe("Content Tests", () => {}); + + // describe("Dimensions Tests", () => {}); + + // describe("Styling Tests", () => {}); + + // describe("Interaction Tests", () => {}); + + // describe("Golden Tests", () => {}); + + // describe("Performance Tests", () => {}); +}); diff --git a/src/test/file-upload/upload-item.test.ts b/src/test/file-upload/upload-item.test.ts new file mode 100644 index 0000000..19d6509 --- /dev/null +++ b/src/test/file-upload/upload-item.test.ts @@ -0,0 +1,68 @@ +import { html } from "lit"; +import { fixture, expect, oneEvent } from "@open-wc/testing"; +import type { ZetaUploadItem } from "../../components/upload-item/upload-item.js"; +import type { ZetaProgressCircle } from "../../components/progress-indicators/progress-indicators.js"; +import { ZetaCloseEvent } from "../../events"; +import "../../components/upload-item/upload-item.js"; + +describe("zeta-upload-item", () => { + // describe("Accessibility Tests", () => {}); + + describe("Content Tests", () => { + it("renders default flavor correctly", async () => { + const element: ZetaUploadItem = await fixture(html` <zeta-upload-item></zeta-upload-item> `); + + await expect(element.flavor).to.equal("default"); + }); + + it("renders completed flavor correctly", async () => { + const element: ZetaUploadItem = await fixture(html` <zeta-upload-item flavor="completed"></zeta-upload-item> `); + + await expect(element.flavor).to.equal("completed"); + + const progressCircle = element.shadowRoot?.querySelector("zeta-progress-circle"); + expect(progressCircle).to.not.exist; + }); + + it("renders error flavor correctly", async () => { + const element: ZetaUploadItem = await fixture(html` <zeta-upload-item flavor="error"></zeta-upload-item> `); + + await expect(element.flavor).to.equal("error"); + + const progressCircle = element.shadowRoot?.querySelector("zeta-progress-circle"); + expect(progressCircle).to.not.exist; + }); + + it("updates progress correctly", async () => { + const element: ZetaUploadItem = await fixture(html` <zeta-upload-item progress="50"></zeta-upload-item> `); + + await expect(element.progress).to.equal(50); + + const progressCircle: ZetaProgressCircle | null | undefined = element.shadowRoot?.querySelector("zeta-progress-circle"); + + await expect(progressCircle?.progress).to.equal(50); + }); + }); + + // describe("Dimensions Tests", () => {}); + + // describe("Styling Tests", () => {}); + + describe("Interaction Tests", () => { + it("fires ZetaCloseEvent:close when close icon is clicked", async () => { + const element: ZetaUploadItem = await fixture(html` <zeta-upload-item flavor="completed"></zeta-upload-item> `); + const eventListener = oneEvent(element, "close"); + + const btn = element.shadowRoot?.querySelector("#cancel") as HTMLElement; + btn.click(); + await element.updateComplete; + + const event = await eventListener; + await expect(event.type).to.equal(new ZetaCloseEvent().name); + }); + }); + + // describe("Golden Tests", () => {}); + + // describe("Performance Tests", () => {}); +}); diff --git a/src/test/global-header/global-header.test.ts b/src/test/global-header/global-header.test.ts new file mode 100644 index 0000000..dc8301f --- /dev/null +++ b/src/test/global-header/global-header.test.ts @@ -0,0 +1,50 @@ +import { fixture, html, unsafeStatic, expect } from "@open-wc/testing"; +import { emulateMedia } from "@web/test-runner-commands"; +import type { ZetaGlobalHeader } from "../../components/global-header/global-header.js"; +import "../../components/global-header/global-header.js"; +import "../../index.css"; +import { getCssVarColorValue } from "../utils.js"; + +describe("zeta-global-header", () => { + let subject: ZetaGlobalHeader; + + const createComponent = (template = `<zeta-global-header></zeta-global-header>`) => { + // prettier-ignore + return fixture<ZetaGlobalHeader>(html`${unsafeStatic(template)}`); + }; + + beforeEach(async () => { + subject = await createComponent(); + }); + + describe("Accessibility Tests", () => { + it("meets accessibility requirements", async () => { + await expect(subject).shadowDom.to.be.accessible(); + }); + }); + + // describe("Content Tests", () => {}); + + // describe("Dimensions Tests", () => {}); + + describe("Styling Tests", () => { + it("renders background color", async () => { + await expect(getComputedStyle(subject!).backgroundColor).to.equal(getCssVarColorValue(subject!, "--surface-default")); + }); + + it("renders background in dark-mode", async () => { + const lightVarColor = getCssVarColorValue(subject!, "--surface-default"); + await emulateMedia({ colorScheme: "dark" }); + const darkSubjectColor = getComputedStyle(subject!).backgroundColor; + const darkVarColor = getCssVarColorValue(subject!, "--surface-default"); + await expect(darkSubjectColor).to.equal(darkVarColor); + await expect(darkSubjectColor).to.not.equal(lightVarColor); + }); + }); + + // describe("Interaction Tests", () => {}); + + // describe("Golden Tests", () => {}); + + // describe("Performance Tests", () => {}); +}); diff --git a/src/test/grid-menu-item/grid-menu-item.test.ts b/src/test/grid-menu-item/grid-menu-item.test.ts new file mode 100644 index 0000000..6a975a3 --- /dev/null +++ b/src/test/grid-menu-item/grid-menu-item.test.ts @@ -0,0 +1,59 @@ +import { expect, fixture, html, unsafeStatic } from "@open-wc/testing"; +import type { ZetaGridMenuItem } from "../../components/grid-menu-item/grid-menu-item.js"; +import { getSlottedIconName, getSlotText } from "../utils.js"; +import "../../components/grid-menu-item/grid-menu-item.js"; + +describe("zeta-grid-menu-item", () => { + const label = "Label"; + const icon = "star"; + const badgeId = "badge"; + + let subject: ZetaGridMenuItem; + + const createComponent = ( + template = `<zeta-grid-menu-item> + <zeta-icon slot="icon">${icon}</zeta-icon> + <zeta-notification-indicator slot="badge" id=${badgeId}>2</zeta-notification-indicator> + ${label} + </zeta-grid-menu-item>` + ) => { + // prettier-ignore + return fixture<ZetaGridMenuItem>(html`${unsafeStatic(template)}`); + }; + + beforeEach(async () => { + subject = await createComponent(); + }); + + describe("Accessibility Tests", () => { + it("meets accessibility requirements", async () => { + await expect(subject).shadowDom.to.be.accessible(); + }); + }); + + describe("Content Tests", () => { + it("renders the given icon", async () => { + await expect(getSlottedIconName(subject)).to.equal(icon); + }); + + it("renders the given label", async () => { + await expect(getSlotText(subject)).to.equal(label); + }); + + it("renders the given badge", () => { + const badgeElement = subject.shadowRoot?.querySelector(`#${badgeId}`); + + expect(badgeElement).to.not.be.undefined; + }); + }); + + // describe("Dimensions Tests", () => {}); + + // describe("Styling Tests", () => {}); + + // describe("Interaction Tests", () => {}); + + // describe("Golden Tests", () => {}); + + // describe("Performance Tests", () => {}); +}); diff --git a/src/test/in-page-banner/in-page-banner.test.ts b/src/test/in-page-banner/in-page-banner.test.ts new file mode 100644 index 0000000..fc45402 --- /dev/null +++ b/src/test/in-page-banner/in-page-banner.test.ts @@ -0,0 +1,59 @@ +import { fixture, html, unsafeStatic, expect } from "@open-wc/testing"; +import type { ZetaInPageBanner } from "../../components/in-page-banner/in-page-banner.js"; +import "../../components/in-page-banner/in-page-banner.js"; + +describe("zeta-in-page-banner", () => { + let subject: ZetaInPageBanner; + + const headingText = "Headline"; + const subHeadingText = "Subhead"; + // const status = "default"; + const status = "info"; + // const status = "positive"; + // const status = "warning"; + // const status = "negative"; + + const createComponent = ( + template = `<zeta-in-page-banner title="${headingText}" status="${status}"> + ${subHeadingText} + </zeta-in-page-banner>` + ) => { + // prettier-ignore + return fixture<ZetaInPageBanner>(html`${unsafeStatic(template)}`); + }; + + beforeEach(async () => { + subject = await createComponent(); + }); + + describe("Accessibility Tests", () => { + it("meets accessibility requirements", async () => { + await expect(subject).shadowDom.to.be.accessible(); + }); + }); + + describe("Content Tests", () => { + it("sets the heading correctly", () => { + const headerElement = subject.shadowRoot?.querySelector(".title"); + + expect(headerElement).to.not.be.undefined; + void expect(headerElement?.textContent).to.equal(headingText); + }); + + it("sets the subheading correctly", async () => { + const slot = subject.shadowRoot!.querySelector("slot"); + const slotContent = (slot?.assignedNodes()[0] as Text)?.data.trim(); + await expect(slotContent).to.equal(subHeadingText); + }); + }); + + // describe("Dimensions Tests", () => {}); + + // describe("Styling Tests", () => {}); + + // describe("Interaction Tests", () => {}); + + // describe("Golden Tests", () => {}); + + // describe("Performance Tests", () => {}); +}); diff --git a/src/test/navigation-bar/navigation-bar.test.ts b/src/test/navigation-bar/navigation-bar.test.ts new file mode 100644 index 0000000..979e2ae --- /dev/null +++ b/src/test/navigation-bar/navigation-bar.test.ts @@ -0,0 +1,139 @@ +import { expect, fixture, html, unsafeStatic } from "@open-wc/testing"; +import type { ZetaNavigationBar } from "../../components/navigation-bar/navigation-bar.js"; +import type { ZetaGridMenuItem } from "../../components/grid-menu-item/grid-menu-item.js"; +import "../../components/grid-menu-item/grid-menu-item.js"; +import "../../components/navigation-bar/navigation-bar.js"; +import "../../components/badges/indicators/indicators.js"; + +describe("zeta-navigation-bar", () => { + const label = "Label"; + const icon = "star"; + const badgeValue = "2"; + + let subject: ZetaNavigationBar; + + const createComponent = ( + template = `<zeta-navigation-bar> + <zeta-grid-menu-item icon=${icon} label=${label}> + <zeta-notification-indicator value=${badgeValue}></zeta-notification-indicator> + </zeta-grid-menu-item> + <zeta-grid-menu-item icon=${icon} label=${label}> + <zeta-notification-indicator value=${badgeValue}></zeta-notification-indicator> + </zeta-grid-menu-item> + <zeta-grid-menu-item icon=${icon} label=${label}> + <zeta-notification-indicator value=${badgeValue}></zeta-notification-indicator> + </zeta-grid-menu-item> + </zeta-navigation-bar>` + ) => { + // prettier-ignore + return fixture<ZetaNavigationBar>(html`${unsafeStatic(template)}`); + }; + + beforeEach(async () => { + subject = await createComponent(); + }); + + describe("Accessibility Tests", () => { + it("meets accessability requirements", async () => { + await expect(subject).shadowDom.to.be.accessible(); + }); + }); + + describe("Content Tests", () => { + it("renders the correct number of navigation bar items", async () => { + const items = subject.querySelectorAll("zeta-grid-menu-item"); + await expect(items.length).to.equal(3); + }); + + it("renders the correct label and icon for each navigation bar item", async () => { + const items = subject.querySelectorAll("zeta-grid-menu-item"); + await Promise.all( + Array.from(items).map(async item => { + const itemLabel = item.getAttribute("label"); + const itemIcon = item.getAttribute("icon"); + await expect(itemLabel).to.equal(label); + await expect(itemIcon).to.equal(icon); + }) + ); + }); + + it("renders the correct badge content for each navigation bar item", async () => { + const badges = subject.querySelectorAll("zeta-notification-indicator"); + await Promise.all( + Array.from(badges).map(async badge => { + await expect(badge.getAttribute("value")).to.equal(badgeValue); + }) + ); + }); + + it("updates badge content when the value changes", async () => { + const newBadgeValue = "5"; + const badges = subject.querySelectorAll("zeta-notification-indicator"); + await Promise.all( + Array.from(badges).map(async badge => { + badge.setAttribute("value", newBadgeValue); + await badge.updateComplete; + await expect(badge.value).to.equal(newBadgeValue); + }) + ); + }); + }); + + // describe("Dimensions Tests", () => {}); + + // describe("Styling Tests", () => {}); + + // describe("Interaction Tests", () => {}); + + // describe("Golden Tests", () => {}); + + // describe("Performance Tests", () => {}); +}); + +describe("zeta-navigation-bar spacer", () => { + const label = "Label"; + const icon = "star"; + const badgeValue = "2"; + + let subject: ZetaNavigationBar; + + const createComponent = ( + template = `<zeta-navigation-bar shrinkItems> + <zeta-grid-menu-item icon=${icon} label=${label}> + <zeta-notification-indicator value=${badgeValue}></zeta-notification-indicator> + </zeta-grid-menu-item> + <div class="spacer"></div> + <zeta-grid-menu-item icon=${icon} label=${label}> + <zeta-notification-indicator value=${badgeValue}></zeta-notification-indicator> + </zeta-grid-menu-item> + </zeta-navigation-bar>` + ) => { + // prettier-ignore + return fixture<ZetaNavigationBar>(html`${unsafeStatic(template)}`); + }; + + beforeEach(async () => { + subject = await createComponent(); + }); + + // describe("Accessibility Tests", () => {}); + + // describe("Content Tests", () => {}); + + describe("Dimensions Tests", () => { + it.skip("keeps a fixed width", async () => { + const item0: ZetaGridMenuItem = subject.querySelector("zeta-grid-menu-item")!; + item0.setAttribute("label", "SomethingVeryVeryLong"); + await item0.updateComplete; + await expect(item0.getBoundingClientRect().width).to.equal(62); + }); + }); + + // describe("Styling Tests", () => {}); + + // describe("Interaction Tests", () => {}); + + // describe("Golden Tests", () => {}); + + // describe("Performance Tests", () => {}); +}); diff --git a/src/test/navigation-drawer-footer/navigation-drawer-footer.test.ts b/src/test/navigation-drawer-footer/navigation-drawer-footer.test.ts new file mode 100644 index 0000000..374f4c2 --- /dev/null +++ b/src/test/navigation-drawer-footer/navigation-drawer-footer.test.ts @@ -0,0 +1,34 @@ +import { fixture, html, unsafeStatic, expect } from "@open-wc/testing"; +import type { ZetaNavigationDrawerFooter } from "../../components/navigation-drawer/navigation-drawer-footer/navigation-drawer-footer.js"; +import "../../components/navigation-drawer/navigation-drawer-footer/navigation-drawer-footer.js"; + +describe("zeta-navigation-drawer-footer", () => { + let subject: ZetaNavigationDrawerFooter; + + const createComponent = (template = `<zeta-navigation-drawer-footer>Title</zeta-navigation-drawer-footer>`) => { + // prettier-ignore + return fixture<ZetaNavigationDrawerFooter>(html`${unsafeStatic(template)}`); + }; + + beforeEach(async () => { + subject = await createComponent(); + }); + + describe("Accessibility Tests", () => { + it("meets accessibility requirements", async () => { + await expect(subject).shadowDom.to.be.accessible(); + }); + }); + + // describe("Content Tests", () => {}); + + // describe("Dimensions Tests", () => {}); + + // describe("Styling Tests", () => {}); + + // describe("Interaction Tests", () => {}); + + // describe("Golden Tests", () => {}); + + // describe("Performance Tests", () => {}); +}); diff --git a/src/test/navigation-drawer-item/navigation-drawer-item.test.ts b/src/test/navigation-drawer-item/navigation-drawer-item.test.ts new file mode 100644 index 0000000..2f7c4ff --- /dev/null +++ b/src/test/navigation-drawer-item/navigation-drawer-item.test.ts @@ -0,0 +1,34 @@ +import { fixture, html, unsafeStatic, expect } from "@open-wc/testing"; +import type { ZetaNavigationDrawerItem } from "../../components/navigation-drawer/navigation-drawer-item/navigation-drawer-item.js"; +import "../../components/navigation-drawer/navigation-drawer-item/navigation-drawer-item.js"; + +describe("zeta-navigation-drawer-item", () => { + let subject: ZetaNavigationDrawerItem; + + const createComponent = (template = `<zeta-navigation-drawer-item>Navigation Item</zeta-navigation-drawer-item>`) => { + // prettier-ignore + return fixture<ZetaNavigationDrawerItem>(html`${unsafeStatic(template)}`); + }; + + beforeEach(async () => { + subject = await createComponent(); + }); + + describe("Accessibility Tests", () => { + it("meets accessibility requirements", async () => { + await expect(subject).shadowDom.to.be.accessible(); + }); + }); + + // describe("Content Tests", () => {}); + + // describe("Dimensions Tests", () => {}); + + // describe("Styling Tests", () => {}); + + // describe("Interaction Tests", () => {}); + + // describe("Golden Tests", () => {}); + + // describe("Performance Tests", () => {}); +}); diff --git a/src/test/navigation-drawer-sub-item/navigation-drawer-sub-item.test.ts b/src/test/navigation-drawer-sub-item/navigation-drawer-sub-item.test.ts new file mode 100644 index 0000000..24f67bd --- /dev/null +++ b/src/test/navigation-drawer-sub-item/navigation-drawer-sub-item.test.ts @@ -0,0 +1,34 @@ +import { fixture, html, unsafeStatic, expect } from "@open-wc/testing"; +import type { ZetaNavigationDrawerSubItem } from "../../components/navigation-drawer/navigation-drawer-sub-item/navigation-drawer-sub-item.js"; +import "../../components/navigation-drawer/navigation-drawer-sub-item/navigation-drawer-sub-item.js"; + +describe("zeta-navigation-drawer-sub-item", () => { + let subject: ZetaNavigationDrawerSubItem; + + const createComponent = (template = `<zeta-navigation-drawer-sub-item>Navigation Item</zeta-navigation-drawer-sub-item>`) => { + // prettier-ignore + return fixture<ZetaNavigationDrawerSubItem>(html`${unsafeStatic(template)}`); + }; + + beforeEach(async () => { + subject = await createComponent(); + }); + + describe("Accessibility Tests", () => { + it("meets accessibility requirements", async () => { + await expect(subject).shadowDom.to.be.accessible(); + }); + }); + + // describe("Content Tests", () => {}); + + // describe("Dimensions Tests", () => {}); + + // describe("Styling Tests", () => {}); + + // describe("Interaction Tests", () => {}); + + // describe("Golden Tests", () => {}); + + // describe("Performance Tests", () => {}); +}); diff --git a/src/test/navigation-drawer/navigation-drawer.test.ts b/src/test/navigation-drawer/navigation-drawer.test.ts new file mode 100644 index 0000000..cdcf6e7 --- /dev/null +++ b/src/test/navigation-drawer/navigation-drawer.test.ts @@ -0,0 +1,34 @@ +import { fixture, html, unsafeStatic, expect } from "@open-wc/testing"; +import type { ZetaNavigationDrawer } from "../../components/navigation-drawer/navigation-drawer.js"; +import "../../components/navigation-drawer/navigation-drawer.js"; + +describe("zeta-navigation-drawer", () => { + let subject: ZetaNavigationDrawer; + + const createComponent = (template = `<zeta-navigation-drawer></zeta-navigation-drawer>`) => { + // prettier-ignore + return fixture<ZetaNavigationDrawer>(html`${unsafeStatic(template)}`); + }; + + beforeEach(async () => { + subject = await createComponent(); + }); + + describe("Accessibility Tests", () => { + it("meets accessibility requirements", async () => { + await expect(subject).shadowDom.to.be.accessible(); + }); + }); + + // describe("Content Tests", () => {}); + + // describe("Dimensions Tests", () => {}); + + // describe("Styling Tests", () => {}); + + // describe("Interaction Tests", () => {}); + + // describe("Golden Tests", () => {}); + + // describe("Performance Tests", () => {}); +}); diff --git a/src/test/navigation-header/navigation-header.test.ts b/src/test/navigation-header/navigation-header.test.ts new file mode 100644 index 0000000..3d6e2c7 --- /dev/null +++ b/src/test/navigation-header/navigation-header.test.ts @@ -0,0 +1,34 @@ +import { fixture, html, unsafeStatic, expect } from "@open-wc/testing"; +import type { ZetaTabBar } from "../../components/tab-bar/tab-bar.js"; +import "../../components/tab-bar/tab-bar.js"; + +describe("zeta-navigation-header", () => { + let subject: ZetaTabBar; + + const createComponent = (template = `<zeta-navigation-header></zeta-navigation-header>`) => { + // prettier-ignore + return fixture<ZetaTabBar>(html`${unsafeStatic(template)}`); + }; + + beforeEach(async () => { + subject = await createComponent(); + }); + + describe("Accessibility Tests", () => { + it("meets accessibility requirements", async () => { + await expect(subject).shadowDom.to.be.accessible(); + }); + }); + + // describe("Content Tests", () => {}); + + // describe("Dimensions Tests", () => {}); + + // describe("Styling Tests", () => {}); + + // describe("Interaction Tests", () => {}); + + // describe("Golden Tests", () => {}); + + // describe("Performance Tests", () => {}); +}); diff --git a/src/test/navigation-profile/navigation-profile.test.ts b/src/test/navigation-profile/navigation-profile.test.ts new file mode 100644 index 0000000..9d5a343 --- /dev/null +++ b/src/test/navigation-profile/navigation-profile.test.ts @@ -0,0 +1,34 @@ +import { fixture, html, unsafeStatic, expect } from "@open-wc/testing"; +import type { ZetaNavigationProfile } from "../../components/navigation-profile/navigation-profile.js"; +import "../../components/navigation-profile/navigation-profile.js"; + +describe("zeta-navigation-profile", () => { + let subject: ZetaNavigationProfile; + + const createComponent = (template = `<zeta-navigation-profile></zeta-navigation-profile>`) => { + // prettier-ignore + return fixture<ZetaNavigationProfile>(html`${unsafeStatic(template)}`); + }; + + beforeEach(async () => { + subject = await createComponent(); + }); + + describe("Accessibility Tests", () => { + it("meets accessibility requirements", async () => { + await expect(subject).shadowDom.to.be.accessible(); + }); + }); + + // describe("Content Tests", () => {}); + + // describe("Dimensions Tests", () => {}); + + // describe("Styling Tests", () => {}); + + // describe("Interaction Tests", () => {}); + + // describe("Golden Tests", () => {}); + + // describe("Performance Tests", () => {}); +}); diff --git a/src/test/navigation-rail/navigation-rail-item.test.ts b/src/test/navigation-rail/navigation-rail-item.test.ts new file mode 100644 index 0000000..9f50639 --- /dev/null +++ b/src/test/navigation-rail/navigation-rail-item.test.ts @@ -0,0 +1,55 @@ +import { html } from "lit"; +import { fixture, expect } from "@open-wc/testing"; +import { getSlotText } from "../utils"; +import "../../components/navigation-rail/navigation-rail-item"; +import type { ZetaNavigationRailItem } from "../../components/navigation-rail/navigation-rail-item"; +import type { ZetaIcon } from "../../components/icon/icon"; +import "../../components/icon/icon"; + +describe("zeta-navigation-rail-item", () => { + // describe("Accessibility Tests", () => {}); + + describe("Content Tests", () => { + it("renders the label slot correctly", async () => { + const label = "Home"; + const element = await fixture(html` <zeta-navigation-rail-item> ${label} </zeta-navigation-rail-item> `); + + await expect(getSlotText(element as HTMLElement)).to.equal(label); + }); + + it("renders the icon slot correctly", async () => { + const iconName = "star"; + const element = await fixture(html` <zeta-navigation-rail-item> <zeta-icon slot="icon">${iconName}</zeta-icon> </zeta-navigation-rail-item> `); + + const icon = (element.shadowRoot?.querySelector("slot[name=icon]") as HTMLSlotElement).assignedElements()[0] as ZetaIcon; + await expect(getSlotText(icon)).to.equal(iconName); + }); + + it("reflects the 'selected' property correctly", async () => { + const element: ZetaNavigationRailItem = await fixture(html`<zeta-navigation-rail-item></zeta-navigation-rail-item>`); + expect(element.selected).to.be.false; + element.selected = true; + await element.updateComplete; + expect(element.selected).to.be.true; + }); + }); + + // describe("Dimensions Tests", () => {}); + + // describe("Styling Tests", () => {}); + + describe("Interaction Tests", () => { + // TODO: figure out how to test navigation. + it.skip("goes to the correct href when clicked", async () => { + const element: ZetaNavigationRailItem = await fixture(html`<zeta-navigation-rail-item href="https://www.google.com"></zeta-navigation-rail-item>`); + element.click(); + + await element.updateComplete; + await expect(window.location.href).to.equal("https://www.google.com"); + }); + }); + + // describe("Golden Tests", () => {}); + + // describe("Performance Tests", () => {}); +}); diff --git a/src/test/navigation-rail/navigation-rail.test.ts b/src/test/navigation-rail/navigation-rail.test.ts new file mode 100644 index 0000000..eb969e6 --- /dev/null +++ b/src/test/navigation-rail/navigation-rail.test.ts @@ -0,0 +1,64 @@ +import { html } from "lit"; +import { fixture, expect } from "@open-wc/testing"; +import "../../components/navigation-rail/navigation-rail"; +import "../../components/navigation-rail/navigation-rail-item"; +import "../../components/icon/icon"; +import { getSlotText } from "../utils"; +import type { ZetaIcon } from "../../components/icon/icon"; + +describe("zeta-navigation-rail", () => { + // describe("Accessibility Tests", () => {}); + + describe("Content Tests", () => { + it("renders the navigation items", async () => { + const items = [ + { label: "Home", icon: "home" }, + { label: "Profile", icon: "person" }, + { label: "Settings", icon: "settings" } + ]; + + const element = await fixture(html` + <zeta-navigation-rail> + ${items.map(item => html` <zeta-navigation-rail-item><zeta-icon slot="icon">${item.icon}</zeta-icon>${item.label} </zeta-navigation-rail-item> `)} + </zeta-navigation-rail> + `); + + const navigationItems = element.querySelectorAll("zeta-navigation-rail-item"); + expect(navigationItems).to.have.lengthOf(items.length); + + let i = 0; + for (const item of navigationItems) { + const label = getSlotText(item); + await expect(label).to.equal(items[i].label); + + const icon = (item.shadowRoot?.querySelector("slot[name=icon]") as HTMLSlotElement).assignedElements()[0] as ZetaIcon; + await expect(getSlotText(icon)).to.equal(items[i].icon); + i++; + } + }); + + it("hides any slotted elements that aren't zeta-navigation-rail-item", async () => { + const items = [ + html`<zeta-navigation-rail-item>Item 1</zeta-navigation-rail-item>`, + html`<div id="unwanted">Item 2</div>`, + html`<zeta-navigation-rail-item>Item 3</zeta-navigation-rail-item>` + ]; + const element = await fixture(html`<zeta-navigation-rail>${items}</zeta-navigation-rail>`); + + const slot = element.shadowRoot?.getElementById("content-slot") as HTMLSlotElement; + const unwantedItem = slot.assignedElements().find(el => el.id === "unwanted") as HTMLDivElement; + + await expect(window.getComputedStyle(unwantedItem).display).to.equal("none"); + }); + }); + + // describe("Dimensions Tests", () => {}); + + // describe("Styling Tests", () => {}); + + // describe("Interaction Tests", () => {}); + + // describe("Golden Tests", () => {}); + + // describe("Performance Tests", () => {}); +}); diff --git a/src/test/pagination/pagination.test.ts b/src/test/pagination/pagination.test.ts new file mode 100644 index 0000000..28f2f4a --- /dev/null +++ b/src/test/pagination/pagination.test.ts @@ -0,0 +1,89 @@ +import { assert, expect, fixture, html } from "@open-wc/testing"; +import { ZetaPagination } from "../../components/pagination/pagination.js"; +import "../../components/pagination/pagination.js"; + +describe("zeta-pagination", () => { + // describe("Accessibility Tests", () => {}); + + describe("Content Tests", () => { + it("creates from document.createElement", function () { + const el = document.createElement("zeta-pagination"); + assert.equal("ZETA-PAGINATION", el.nodeName); + }); + + it("creates from constructor", function () { + const el = new ZetaPagination(); + assert.equal("ZETA-PAGINATION", el.nodeName); + }); + + it("should disable left controls", async () => { + const el = await fixture(html`<zeta-pagination></zeta-pagination>`); + const prevButton = el.shadowRoot?.querySelector("zeta-icon-button.chevron_left") as HTMLElement; + const startButton = el.shadowRoot?.querySelector("zeta-icon-button.first_page") as HTMLElement; + expect(prevButton).to.have.attribute("disabled"); + expect(startButton).to.have.attribute("disabled"); + }); + + it("should disable right controls", async () => { + const el = await fixture(html`<zeta-pagination currentPage="10"></zeta-pagination>`); + const nextButton = el.shadowRoot?.querySelector("zeta-icon-button.chevron_right") as HTMLElement; + const endButton = el.shadowRoot?.querySelector("zeta-icon-button.last_page") as HTMLElement; + expect(nextButton).to.have.attribute("disabled"); + expect(endButton).to.have.attribute("disabled"); + }); + + it("should not set invalid page", async () => { + // prettier-ignore + const el = await fixture<ZetaPagination>(html`<zeta-pagination totalPages="10" currentPage="15"></zeta-pagination>`); + assert.equal(el.currentPage, 10); + }); + + it("should not set invalid page", async () => { + // prettier-ignore + const el = await fixture<ZetaPagination>(html`<zeta-pagination totalPages="10" currentPage="-1"></zeta-pagination>`); + assert.equal(el.currentPage, 1); + }); + + it("should show left and right dots", async () => { + // prettier-ignore + const el = await fixture<ZetaPagination>(html`<zeta-pagination totalPages="10" currentPage="4"></zeta-pagination>`); + const dots = el.shadowRoot?.querySelectorAll("zeta-icon.more"); + assert.equal(dots?.length, 2); + }); + + it("should not show any dots", async () => { + // prettier-ignore + const el = await fixture<ZetaPagination>(html`<zeta-pagination totalPages="5"></zeta-pagination>`); + const dots = el.shadowRoot?.querySelectorAll("zeta-icon.more"); + assert.equal(dots?.length, 0); + }); + }); + + // describe("Dimensions Tests", () => {}); + + // describe("Styling Tests", () => {}); + + describe("Interaction Tests", () => { + it("should increment page", async () => { + // prettier-ignore + const el = await fixture<ZetaPagination>(html`<zeta-pagination></zeta-pagination>`); + const next = el.shadowRoot?.querySelector("zeta-icon-button.chevron_right") as HTMLElement; + next.click(); + await el.updateComplete; + assert.equal(el.currentPage, 2); + }); + + it("should decrement page", async () => { + // prettier-ignore + const el = await fixture<ZetaPagination>(html`<zeta-pagination currentPage="3"></zeta-pagination>`); + const prev = el.shadowRoot?.querySelector("zeta-icon-button.chevron_left") as HTMLElement; + prev.click(); + await el.updateComplete; + assert.equal(el.currentPage, 2); + }); + }); + + // describe("Golden Tests", () => {}); + + // describe("Performance Tests", () => {}); +}); diff --git a/src/test/progress-bar/progress-bar.test.ts b/src/test/progress-bar/progress-bar.test.ts new file mode 100644 index 0000000..e09f9e3 --- /dev/null +++ b/src/test/progress-bar/progress-bar.test.ts @@ -0,0 +1,34 @@ +import { fixture, html, unsafeStatic, expect } from "@open-wc/testing"; +import type { ZetaProgressBar } from "../../components/progress-indicators/progress-bar/progress-bar.js"; +import "../../components/progress-indicators/progress-bar/progress-bar.js"; + +describe("zeta-progress-bar", () => { + let subject: ZetaProgressBar; + + const createComponent = (template = `<zeta-progress-bar></zeta-progress-bar>`) => { + // prettier-ignore + return fixture<ZetaProgressBar>(html`${unsafeStatic(template)}`); + }; + + beforeEach(async () => { + subject = await createComponent(); + }); + + describe("Accessibility Tests", () => { + it("meets accessibility requirements", async () => { + await expect(subject).shadowDom.to.be.accessible(); + }); + }); + + // describe("Content Tests", () => {}); + + // describe("Dimensions Tests", () => {}); + + // describe("Styling Tests", () => {}); + + // describe("Interaction Tests", () => {}); + + // describe("Golden Tests", () => {}); + + // describe("Performance Tests", () => {}); +}); diff --git a/src/test/progress-circle/progress-circle.test.ts b/src/test/progress-circle/progress-circle.test.ts new file mode 100644 index 0000000..dbba353 --- /dev/null +++ b/src/test/progress-circle/progress-circle.test.ts @@ -0,0 +1,76 @@ +import { assert, expect, fixture, html, oneEvent } from "@open-wc/testing"; +import { ZetaProgressCircle } from "../../components/progress-indicators/progress-circle/progress-circle.js"; +import "../../components/progress-indicators/progress-circle/progress-circle.js"; +import { ZetaCancelUploadEvent } from "../../events.js"; + +describe("ZetaProgressCircle", () => { + // describe("Accessibility Tests", () => {}); + + describe("Content Tests", () => { + it("creates from document.createElement", function () { + const el = document.createElement("zeta-progress-circle"); + assert.equal("ZETA-PROGRESS-CIRCLE", el.nodeName); + }); + + it("creates from constructor", function () { + const el = new ZetaProgressCircle(); + assert.equal("ZETA-PROGRESS-CIRCLE", el.nodeName); + }); + + it("should set value to 100 if above 100", async () => { + // prettier-ignore + const el = await fixture<ZetaProgressCircle>(html`<zeta-progress-circle></zeta-progress-circle>`); + el.progress = 101; + await el.updateComplete; + assert.equal(el.progress, 100); + }); + + it("should set value to 0 if below 0", async () => { + // prettier-ignore + const el = await fixture<ZetaProgressCircle>(html`<zeta-progress-circle></zeta-progress-circle>`); + el.progress = -1; + await el.updateComplete; + assert.equal(el.progress, 0); + }); + + it("should set value ", async () => { + // prettier-ignore + const el = await fixture<ZetaProgressCircle>(html`<zeta-progress-circle progress="50"></zeta-progress-circle>`); + assert.equal(el.progress, 50); + }); + + it("should render uploading components", async () => { + // prettier-ignore + const el = await fixture<ZetaProgressCircle>(html`<zeta-progress-circle></zeta-progress-circle>`); + el.type = "upload"; + await el.updateComplete; + expect(el.shadowRoot?.querySelector(".uploading")).to.exist; + }); + }); + + // describe("Dimensions Tests", () => {}); + + // describe("Styling Tests", () => {}); + + describe("Interaction Tests", () => { + it("should fire a ZetaCancelUploadEvent on button click", async () => { + // prettier-ignore + const el = await fixture<ZetaProgressCircle>(html`<zeta-progress-circle></zeta-progress-circle>`); + const eventListener = oneEvent(el, "cancel-upload"); + + el.type = "upload"; + await el.updateComplete; + + const btn = el.shadowRoot?.querySelector(".cancel") as HTMLElement; + btn.click(); + await el.updateComplete; + + const event = await eventListener; + assert(event.type, new ZetaCancelUploadEvent().name); + }); + }); + + // describe("Golden Tests", () => {}); + + // describe("Performance Tests", () => {}); +}); diff --git a/src/test/radio-button/radio-button.test.ts b/src/test/radio-button/radio-button.test.ts new file mode 100644 index 0000000..f2f7f47 --- /dev/null +++ b/src/test/radio-button/radio-button.test.ts @@ -0,0 +1,122 @@ +import { fixture, html, unsafeStatic, expect, elementUpdated } from "@open-wc/testing"; +import type { ZetaRadioButton } from "../../components/radio-button/radio-button.js"; +import "../../components/radio-button/radio-button.js"; + +describe("zeta-radio-button", () => { + let subject: ZetaRadioButton; + + const createComponent = (template = `<zeta-radio-button></zeta-radio-button>`) => { + // prettier-ignore + return fixture<ZetaRadioButton>(html`${unsafeStatic(template)}`); + }; + + beforeEach(async () => { + subject = await createComponent(); + }); + + /** TODO need a full reassess of form control labels */ + describe.skip("Accessibility Tests", () => { + it("meets accessibility requirements", async () => { + const el = await fixture(html`<label for="checky">Label</label><zeta-radio-button name="checky"></zeta-radio-button>`); + await expect(el).to.be.accessible(); + }); + + it("doesn't meet accessibility requirement without a label", async () => { + const el = await fixture(html`<zeta-radio-button></zeta-radio-button>`); + await expect(el).not.to.be.accessible(); + }); + + it("is accessible by adding aria-label", async () => { + const el = await fixture(html`<zeta-radio-button aria-label="Test radio button"></zeta-radio-button>`); + await expect(el).to.be.accessible(); + }); + }); + + describe("Content Tests", () => { + it("sets the name attribute correctly", async () => { + const name = "myRadioButton"; + subject.name = name; + await elementUpdated(subject); + + const inputElement = subject.shadowRoot?.querySelector("input"); + expect(inputElement).to.not.be.undefined; + await expect(inputElement?.getAttribute("name")).to.equal(name); + }); + + it("sets the id attribute correctly", async () => { + const id = "myRadioButton"; + subject.id = id; + await elementUpdated(subject); + + const inputElement = subject.shadowRoot?.querySelector("input"); + expect(inputElement).to.not.be.undefined; + await expect(inputElement?.getAttribute("id")).to.equal(id); + }); + + it("renders the radio button as unchecked by default", async () => { + await expect(subject.checked).to.equal(false); + }); + }); + + // describe("Dimensions Tests", () => {}); + + // describe("Styling Tests", () => {}); + + describe("Interaction Tests", () => { + it("changes the checked state when clicked", async () => { + (subject.shadowRoot?.querySelector(".container") as HTMLElement)?.click(); + await expect(subject.checked).to.equal(true); + }); + }); + + // describe("Golden Tests", () => {}); + + // describe("Performance Tests", () => {}); +}); + +describe("zeta-radio-button label", () => { + let subject: ZetaRadioButton; + const labelTest = "Test Label"; + + const createComponent = (template = `<zeta-radio-button>${labelTest}</zeta-radio-button>`) => { + // prettier-ignore + return fixture<ZetaRadioButton>(html`${unsafeStatic(template)}`); + }; + + beforeEach(async () => { + subject = await createComponent(); + }); + + // describe("Accessibility Tests", () => {}); + + describe("Content Tests", () => { + it("renders the radio label correctly", async () => { + const slot = subject.shadowRoot?.querySelector("slot"); + await expect( + slot + ?.assignedNodes() + .map(a => a.nodeValue) + .join() + ).to.equal(labelTest); + }); + }); + + // describe("Dimensions Tests", () => {}); + + // describe("Styling Tests", () => {}); + + describe("Interaction Tests", () => { + it("checks the radio when label is clicked", async () => { + const labelText = subject.shadowRoot?.querySelector("label"); + expect(subject.getAttribute("checked")).to.be.null; + labelText?.click(); + await elementUpdated(subject); + await expect(subject.getAttribute("checked")).to.be.equal(""); + expect(subject.checked).to.be.true; + }); + }); + + // describe("Golden Tests", () => {}); + + // describe("Performance Tests", () => {}); +}); diff --git a/src/test/range-selector/range-selector.test.ts b/src/test/range-selector/range-selector.test.ts new file mode 100644 index 0000000..a52c9b7 --- /dev/null +++ b/src/test/range-selector/range-selector.test.ts @@ -0,0 +1,185 @@ +import { fixture, html, expect, unsafeStatic } from "@open-wc/testing"; +import { type ZetaRangeSelector } from "../../components/slider/range-selector/range-selector.js"; +import "../../components/slider/range-selector/range-selector.js"; + +import "../../index.css"; +import { getCssVarColorValue } from "../utils.js"; + +describe("zeta-range-selector", () => { + let subject: ZetaRangeSelector; + + const createComponent = (template = `<zeta-range-selector></zeta-range-selector>`) => { + // prettier-ignore + return fixture<ZetaRangeSelector>(html`${unsafeStatic(template)}`); + }; + + beforeEach(async () => { + subject = await createComponent(); + }); + + describe("Accessibility Tests", () => { + it("meets accessibility requirements", async () => { + await expect(subject).shadowDom.to.be.accessible(); + }); + }); + + describe("Content Tests", () => { + it("renders the zeta slider", () => { + const slider = subject.shadowRoot?.querySelector("zeta-slider"); + expect(slider).to.exist; + }); + + it("renders the label", async () => { + subject.label = "Label"; + await subject.updateComplete; + + const label = subject.shadowRoot?.querySelector("label"); + expect(label).to.exist; + + const labelText = label?.textContent; + await expect(labelText).to.equal("Label"); + }); + + it("renders the lower input field", async () => { + const input = subject.shadowRoot?.querySelector(".lower-input") as HTMLInputElement; + expect(input).to.exist; + + const inputValue = input?.value; + await expect(inputValue).to.equal("10"); + }); + + it("renders the upper input field", async () => { + const input = subject.shadowRoot?.querySelector(".upper-input") as HTMLInputElement; + expect(input).to.exist; + + const inputValue = input?.value; + await expect(inputValue).to.equal("90"); + }); + + it("sets the initial values correctly", async () => { + await expect(subject.initialValues.min).to.equal(10); + await expect(subject.initialValues.max).to.equal(90); + await expect(subject.min).to.equal(0); + await expect(subject.max).to.equal(100); + await expect(subject.stepIncrement).to.equal(undefined); + await expect(subject.label).to.equal(undefined); + await expect(subject.disabled).to.equal(false); + await expect(subject.error).to.equal(false); + await expect(subject.rounded).to.equal(undefined); + await expect(subject.name).to.equal(""); + }); + }); + + describe("Dimensions Tests", () => { + it("sets the lower input width and height correctly", async () => { + const lowerInput = subject.shadowRoot?.querySelector("input.lower-input"); + const inputPaddingLeft = getComputedStyle(lowerInput!).paddingLeft?.split("px")?.shift(); + const inputPaddingRight = getComputedStyle(lowerInput!).paddingRight?.split("px")?.shift(); + const inputWidth = 56 + parseInt(inputPaddingLeft!) + parseInt(inputPaddingRight!); + + const inputPaddingTop = getComputedStyle(lowerInput!).paddingTop?.split("px")?.shift(); + const inputPaddingBottom = getComputedStyle(lowerInput!).paddingBottom?.split("px")?.shift(); + const inputHeight = +48 + parseInt(inputPaddingTop!) + parseInt(inputPaddingBottom!); + + await expect(lowerInput?.clientWidth).to.equal(inputWidth); + await expect(lowerInput?.clientHeight).to.equal(inputHeight); + }); + + it("sets the upper input width and height correctly", async () => { + const upperInput = subject.shadowRoot?.querySelector("input.upper-input"); + const upperinputPaddingLeft = getComputedStyle(upperInput!).paddingLeft?.split("px")?.shift(); + const upperinputPaddingRight = getComputedStyle(upperInput!).paddingRight?.split("px")?.shift(); + const upperInputWidth = 56 + parseInt(upperinputPaddingLeft!) + parseInt(upperinputPaddingRight!); + + const upperInputPaddingTop = getComputedStyle(upperInput!).paddingTop?.split("px")?.shift(); + const upperInputPaddingBottom = getComputedStyle(upperInput!).paddingBottom?.split("px")?.shift(); + const upperInputHeight = +48 + parseInt(upperInputPaddingTop!) + parseInt(upperInputPaddingBottom!); + + await expect(upperInput?.clientWidth).to.equal(upperInputWidth); + await expect(upperInput?.clientHeight).to.equal(upperInputHeight); + }); + }); + + describe("Styling Tests", () => { + it("sets the correct styles for the label", async () => { + subject.label = "Label"; + await subject.updateComplete; + const label = subject.shadowRoot?.querySelector("label"); + + const labelFontSize = getComputedStyle(label!).fontSize; + const labelFontWeight = getComputedStyle(label!).fontWeight; + const labelColor = getComputedStyle(label!).color; + + await expect(labelFontSize).to.equal("16px"); + await expect(labelFontWeight).to.equal("400"); + await expect(labelColor).to.equal(getCssVarColorValue(label as Element, "--main-default")); + }); + + it("sets the correct styles for the lower input", async () => { + const input = subject.shadowRoot?.querySelector("input.lower-input"); + + const inputFontSize = getComputedStyle(input!).fontSize; + const inputFontWeight = getComputedStyle(input!).fontWeight; + const inputColor = getComputedStyle(input!).color; + + await expect(inputFontSize).to.equal("16px"); + await expect(inputFontWeight).to.equal("400"); + await expect(inputColor).to.equal(getCssVarColorValue(input as Element, "--main-subtle")); + }); + + it("sets the correct styles for the upper input", async () => { + const input = subject.shadowRoot?.querySelector("input.upper-input"); + + const inputFontSize = getComputedStyle(input!).fontSize; + const inputFontWeight = getComputedStyle(input!).fontWeight; + const inputColor = getComputedStyle(input!).color; + + await expect(inputFontSize).to.equal("16px"); + await expect(inputFontWeight).to.equal("400"); + await expect(inputColor).to.equal(getCssVarColorValue(input as Element, "--main-subtle")); + }); + }); + + describe("Interaction Tests", () => { + it("updates the hidden input value when the slider value changes", async () => { + const slider = subject.shadowRoot?.querySelector("zeta-slider"); + slider?.dispatchEvent(new CustomEvent("zeta-range-slider-change", { detail: { min: 25, max: 75 } })); + + const input = subject.shadowRoot?.querySelector("input#hidden-range-selector-input") as HTMLInputElement; + const inputValue = input?.value; + await expect(inputValue).to.equal("25-75"); + }); + + it("updates the slider value when the lower input value changes", async () => { + const input = subject.shadowRoot?.querySelector("input.lower-input") as HTMLInputElement; + if (input) { + input.value = "25"; + input.dispatchEvent(new Event("input")); + } + + await subject.updateComplete; + + const slider = subject.shadowRoot?.querySelector("zeta-slider"); + const sliderValue = slider?.getAttribute("lowerValue"); + await expect(sliderValue).to.equal("25"); + }); + + it("updates the slider value when the lower input value changes", async () => { + const input = subject.shadowRoot?.querySelector("input.upper-input") as HTMLInputElement; + if (input) { + input.value = "75"; + input.dispatchEvent(new Event("input")); + } + + await subject.updateComplete; + + const slider = subject.shadowRoot?.querySelector("zeta-slider"); + const sliderValue = slider?.getAttribute("upperValue"); + await expect(sliderValue).to.equal("75"); + }); + }); + + describe("Golden Tests", () => {}); + + describe("Performance Tests", () => {}); +}); diff --git a/src/test/search/search.test.ts b/src/test/search/search.test.ts new file mode 100644 index 0000000..b27aef1 --- /dev/null +++ b/src/test/search/search.test.ts @@ -0,0 +1,152 @@ +import { assert, fixture, expect, html } from "@open-wc/testing"; +import type { ZetaIcon } from "../../components/icon/icon.js"; +import { ZetaSearch } from "../../components/search/search.js"; +import { MouseActions, getCssVarColorValue } from "../utils.js"; +import "../../components/search/search.js"; +import "../../index.css"; + +//TODO keyboard Tab doesnt seem to work +//TODO test enter (submit) and escape (clear) keys +//TODO test the input event +describe("zeta-search", () => { + // describe("Accessibility Tests", () => {}); + + describe("Content Tests", () => { + it("creates from document.createElement", function () { + const el = document.createElement("zeta-search"); + assert.equal("ZETA-SEARCH", el.nodeName); + }); + + it("creates from constructor", function () { + const el = new ZetaSearch(); + assert.equal("ZETA-SEARCH", el.nodeName); + }); + + it("focus on input when field focused", async () => { + // prettier-ignore + const el = await fixture<ZetaSearch>(html` <zeta-search></zeta-search> `); + el.focus(); + expect(el.shadowRoot?.querySelector("input:focus")).to.exist; + }); + + it("blur input when field is blurred", async () => { + // prettier-ignore + const el = await fixture<ZetaSearch>(html` <zeta-search></zeta-search> `); + el.focus(); + el.blur(); + expect(el.shadowRoot?.querySelector("input:focus")).not.to.exist; + }); + + it("should render microphone icon", async () => { + const speechRecognition = (<any>window).SpeechRecognition || (<any>window).webkitSpeechRecognition; + if (speechRecognition) { + // prettier-ignore + const el = await fixture<ZetaSearch>(html` <zeta-search hasIcon></zeta-search> `); + const icons = [...el.shadowRoot!.querySelectorAll("zeta-icon")]; + expect(icons).to.exist; + expect(icons).to.have.lengthOf(2); + const icon = icons.find((e: any) => e.innerText === "microphone"); + expect(icon).to.exist; + } + }); + + it("should not render microphone icon", async () => { + const el = await fixture<ZetaSearch>(html` <zeta-search hasIcon></zeta-search> `); + delete (<any>window).SpeechRecognition; + delete (<any>window).webkitSpeechRecognition; + el.requestUpdate(); + await el.updateComplete; + const icon = el.shadowRoot?.querySelectorAll("zeta-icon"); + /// If only 1 icon displayed, that will be the search icon, not microphone. + expect(icon).to.have.lengthOf(1); + }); + }); + + describe("Dimensions Tests", () => { + it("should set correct default icon size", async () => { + // prettier-ignore + const el = await fixture<ZetaSearch>(html` <zeta-search></zeta-search> `); + const icon = el.shadowRoot?.querySelectorAll("zeta-icon")[0] as ZetaIcon; + await expect(getComputedStyle(icon).fontSize).to.equal("20px"); + }); + + it("should set correct small icon size", async () => { + // prettier-ignore + const el = await fixture<ZetaSearch>(html` <zeta-search></zeta-search> `); + el.size = "small"; + await el.updateComplete; + const icon = el.shadowRoot?.querySelectorAll("zeta-icon")[0] as ZetaIcon; + await expect(getComputedStyle(icon).fontSize).to.equal("16px"); + }); + + it("should set correct large icon size", async () => { + // prettier-ignore + const el = await fixture<ZetaSearch>(html` <zeta-search></zeta-search> `); + el.size = "large"; + await el.updateComplete; + const icon = el.shadowRoot?.querySelectorAll("zeta-icon")[0] as ZetaIcon; + await expect(getComputedStyle(icon).fontSize).to.equal("24px"); + }); + }); + + describe("Styling Tests", () => { + it("should set correct disabled icon color", async () => { + // prettier-ignore + const el = await fixture<ZetaSearch>(html` <zeta-search></zeta-search> `); + el.disabled = true; + await el.updateComplete; + const icon = el.shadowRoot?.querySelectorAll("zeta-icon")[0] as ZetaIcon; + await expect(getComputedStyle(icon).color).to.equal(getCssVarColorValue(icon, "--main-disabled")); + }); + }); + + describe("Interaction Tests", () => { + it("clears input", async () => { + // prettier-ignore + const el = await fixture<ZetaSearch>(html` <zeta-search></zeta-search> `); + el.value = "change"; + await el.updateComplete; + const icon = el.shadowRoot?.querySelectorAll("zeta-icon")[1] as ZetaIcon; + icon.click(); + await el.updateComplete; + assert.equal(el.value, ""); + }); + + it("should call input onchange", async () => { + // prettier-ignore + const el = await fixture<ZetaSearch>(html` <zeta-search></zeta-search> `); + const input = el.shadowRoot?.querySelector("input"); + input!.value = "change"; + input?.dispatchEvent(new Event("change", { bubbles: true })); + assert.equal(el.value, "change"); + }); + + let subject: ZetaSearch; + let interactiveTarget: HTMLFormElement; + + it.skip("should not be activatable", async () => { + // prettier-ignore + subject = await fixture<ZetaSearch>(html`<zeta-search></zeta-search>`); + interactiveTarget = subject.shadowRoot!.querySelector(".interactive-target") as HTMLFormElement; + + let subjectStyles = getComputedStyle(subject!); + let interactiveTargetStyles = getComputedStyle(interactiveTarget!); + await expect(subjectStyles.outlineWidth).to.equal("0px"); + await expect(subjectStyles.outlineStyle).to.equal("none"); + await expect(interactiveTargetStyles.outlineWidth).to.equal("0px"); + await expect(interactiveTargetStyles.outlineStyle).to.equal("none"); + await MouseActions.down(subject); + + subjectStyles = getComputedStyle(subject!); + interactiveTargetStyles = getComputedStyle(interactiveTarget!); + await expect(subjectStyles.outlineWidth).to.equal("0px"); + await expect(subjectStyles.outlineStyle).to.equal("none"); + await expect(interactiveTargetStyles.outlineWidth).to.equal("0px"); + await expect(interactiveTargetStyles.outlineStyle).to.equal("none"); + }); + }); + + // describe("Golden Tests", () => {}); + + // describe("Performance Tests", () => {}); +}); diff --git a/src/test/segmented-control/segmented-control.test.ts b/src/test/segmented-control/segmented-control.test.ts new file mode 100644 index 0000000..2d14541 --- /dev/null +++ b/src/test/segmented-control/segmented-control.test.ts @@ -0,0 +1,70 @@ +import { html } from "lit"; +import { fixture, expect } from "@open-wc/testing"; +import type { ZetaSegmentedControl } from "../../components/segmented-control/segmented-control"; +import "../../components/segmented-control/segmented-control.js"; + +describe("zeta-segmented-control", () => { + // describe("Accessibility Tests", () => {}); + + describe("Content Tests", () => { + it("renders the segmented control with the correct number of items", async () => { + const items = [ + html`<zeta-segmented-item>Item 1</zeta-segmented-item>`, + html`<zeta-segmented-item>Item 2</zeta-segmented-item>`, + html`<zeta-segmented-item>Item 3</zeta-segmented-item>` + ]; + const element: ZetaSegmentedControl = await fixture(html`<zeta-segmented-control>${items}</zeta-segmented-control>`); + + await expect(element.items.length).to.equal(3); + }); + + it("sets the first item as active by default", async () => { + const items = [ + html`<zeta-segmented-item>Item 1</zeta-segmented-item>`, + html`<zeta-segmented-item>Item 2</zeta-segmented-item>`, + html`<zeta-segmented-item>Item 3</zeta-segmented-item>` + ]; + const element: ZetaSegmentedControl = await fixture(html`<zeta-segmented-control>${items}</zeta-segmented-control>`); + + await expect(element.activeItem).to.equal(element.items[0]); + }); + + it("hides any slotted elements that aren't zeta-segmented-item", async () => { + const items = [ + html`<zeta-segmented-item>Item 1</zeta-segmented-item>`, + html`<div id="unwanted">Item 2</div>`, + html`<zeta-segmented-item>Item 3</zeta-segmented-item>` + ]; + const element: ZetaSegmentedControl = await fixture(html`<zeta-segmented-control>${items}</zeta-segmented-control>`); + + const slot = element.shadowRoot?.getElementById("content-slot") as HTMLSlotElement; + const unwantedItem = slot.assignedElements().find(el => el.id === "unwanted") as HTMLDivElement; + + await expect(window.getComputedStyle(unwantedItem).display).to.equal("none"); + }); + }); + + // describe("Dimensions Tests", () => {}); + + // describe("Styling Tests", () => {}); + + describe("Interaction Tests", () => { + it("updates the active item when a different item is clicked", async () => { + const items = [ + html`<zeta-segmented-item>Item 1</zeta-segmented-item>`, + html`<zeta-segmented-item>Item 2</zeta-segmented-item>`, + html`<zeta-segmented-item>Item 3</zeta-segmented-item>` + ]; + const element: ZetaSegmentedControl = await fixture(html`<zeta-segmented-control>${items}</zeta-segmented-control>`); + + const item2 = element.items[1]; + item2.click(); + + await expect(element.activeItem).to.equal(item2); + }); + }); + + // describe("Golden Tests", () => {}); + + // describe("Performance Tests", () => {}); +}); diff --git a/src/test/slider-input-field/slider-input-field.test.ts b/src/test/slider-input-field/slider-input-field.test.ts new file mode 100644 index 0000000..510085a --- /dev/null +++ b/src/test/slider-input-field/slider-input-field.test.ts @@ -0,0 +1,189 @@ +import { fixture, html, unsafeStatic, expect } from "@open-wc/testing"; +import type { ZetaSliderInputField } from "../../components/slider/slider-input-field/slider-input-field.js"; +import "../../components/slider/slider-input-field/slider-input-field.js"; +import "../../index.css"; +import { getCssVarColorValue } from "../utils.js"; +import type { FormEvent } from "react"; + +describe("zeta-slider-input-field", () => { + let subject: ZetaSliderInputField; + + const createComponent = (template = `<zeta-slider-input-field label="Label"></zeta-slider-input-field>`) => { + // prettier-ignore + return fixture<ZetaSliderInputField>(html`${unsafeStatic(template)}`); + }; + + beforeEach(async () => { + subject = await createComponent(); + await subject.updateComplete; + }); + + describe("Accessibility Tests", () => { + it("meets accessibility requirements", async () => { + await expect(subject).shadowDom.to.be.accessible(); + }); + }); + + describe("Content Tests", () => { + it("renders the zeta slider", () => { + const slider = subject.shadowRoot?.querySelector("zeta-slider"); + expect(slider).to.exist; + }); + + it("renders the label", async () => { + subject.label = "Label"; + await subject.updateComplete; + + const label = subject.shadowRoot?.querySelector("label"); + expect(label).to.exist; + + const labelText = label?.textContent; + await expect(labelText).to.equal("Label"); + }); + + it("renders the input field", async () => { + const input = subject.shadowRoot?.querySelector("input"); + expect(input).to.exist; + + const inputValue = input?.value; + await expect(inputValue).to.equal("50"); + }); + + it("renders the min and max labels", async () => { + const minLabel = subject.shadowRoot?.querySelector(".range-label-container p:first-child"); + expect(minLabel).to.exist; + + const minLabelText = minLabel?.textContent; + await expect(minLabelText).to.equal("0"); + + const maxLabel = subject.shadowRoot?.querySelector(".range-label-container p:last-child"); + expect(maxLabel).to.exist; + + const maxLabelText = maxLabel?.textContent; + await expect(maxLabelText).to.equal("100"); + }); + + it("sets the initial values correctly", async () => { + await expect(subject.initialValue).to.equal(50); + await expect(subject.min).to.equal(0); + await expect(subject.max).to.equal(100); + await expect(subject.stepIncrement).to.equal(undefined); + await expect(subject.label).to.equal("Label"); + await expect(subject.disabled).to.equal(false); + await expect(subject.error).to.equal(false); + await expect(subject.rounded).to.equal(undefined); + await expect(subject.name).to.equal(""); + }); + }); + + describe("Dimensions Tests", () => { + it("sets the input width and height correctly", async () => { + const input = subject.shadowRoot?.querySelector("input.contourable-target"); + const inputPaddingLeft = getComputedStyle(input!).paddingLeft?.split("px")?.shift(); + const inputPaddingRight = getComputedStyle(input!).paddingRight?.split("px")?.shift(); + const inputWidth = 56 + parseInt(inputPaddingLeft!) + parseInt(inputPaddingRight!); + + const inputPaddingTop = getComputedStyle(input!).paddingTop?.split("px")?.shift(); + const inputPaddingBottom = getComputedStyle(input!).paddingBottom?.split("px")?.shift(); + const inputHeight = +48 + parseInt(inputPaddingTop!) + parseInt(inputPaddingBottom!); + + await expect(input?.clientWidth).to.equal(inputWidth); + await expect(input?.clientHeight).to.equal(inputHeight); + }); + }); + + describe("Styling Tests", () => { + it("sets the correct styles for the label", async () => { + subject.label = "Label"; + await subject.updateComplete; + const label = subject.shadowRoot?.querySelector("label"); + + const labelFontSize = getComputedStyle(label!).fontSize; + const labelFontWeight = getComputedStyle(label!).fontWeight; + const labelColor = getComputedStyle(label!).color; + + await expect(labelFontSize).to.equal("16px"); + await expect(labelFontWeight).to.equal("400"); + await expect(labelColor).to.equal(getCssVarColorValue(label as Element, "--main-default")); + }); + + it("sets the correct styles for the input", async () => { + const input = subject.shadowRoot?.querySelector("input"); + + const inputFontSize = getComputedStyle(input!).fontSize; + const inputFontWeight = getComputedStyle(input!).fontWeight; + const inputColor = getComputedStyle(input!).color; + + await expect(inputFontSize).to.equal("16px"); + await expect(inputFontWeight).to.equal("400"); + await expect(inputColor).to.equal(getCssVarColorValue(input as Element, "--main-subtle")); + }); + + it("sets the correct styles for the range labels", async () => { + const rangeLabel = subject.shadowRoot?.querySelector(".range-label-container p:first-child"); + + const rangeLabelFontSize = getComputedStyle(rangeLabel!).fontSize; + const rangeLabelFontWeight = getComputedStyle(rangeLabel!).fontWeight; + const rangeLabelColor = getComputedStyle(rangeLabel!).color; + + await expect(rangeLabelFontSize).to.equal("16px"); + await expect(rangeLabelFontWeight).to.equal("400"); + await expect(rangeLabelColor).to.equal(getCssVarColorValue(rangeLabel!, "--main-default")); + }); + }); + + describe("Interaction Tests", () => { + it("updates the hidden input value when the slider value changes", async () => { + const slider = subject.shadowRoot?.querySelector("zeta-slider"); + slider?.dispatchEvent(new CustomEvent("zeta-slider-change", { detail: { value: 75 } })); + + const input = subject.shadowRoot?.querySelector("input#hidden-slider-input") as HTMLInputElement; + const inputValue = input?.value; + await expect(inputValue).to.equal("75"); + }); + + it("updates the slider value when the input value changes", async () => { + const input = subject.shadowRoot?.querySelector("input.contourable-target") as HTMLInputElement; + if (input) { + input.value = "25"; + input.dispatchEvent(new Event("input")); + } + + await subject.updateComplete; + + const slider = subject.shadowRoot?.querySelector("zeta-slider"); + const sliderValue = slider?.getAttribute("value"); + await expect(sliderValue).to.equal("25"); + }); + + it("returns the correct data when in a form", async () => { + let value = 0; + const form: HTMLFormElement = await fixture( + html`<form + @submit=${(e: FormEvent) => { + e.preventDefault(); + const data = new FormData(e.target as HTMLFormElement); + value = parseInt(data.get("test") as string); + }} + > + <zeta-slider-input-field name="test"></zeta-slider-input-field><button type="submit">Submit</button> + </form>` + ); + const slider = form.querySelector("zeta-slider-input-field") as ZetaSliderInputField; + const button = form.querySelector("button") as HTMLButtonElement; + + const zetaSlider = slider.shadowRoot?.querySelector("zeta-slider"); + zetaSlider?.dispatchEvent(new CustomEvent("zeta-slider-change", { detail: { value: 75 } })); + + if (button) { + button.click(); + } + + await expect(value).to.equal(75); + }); + }); + + describe("Golden Tests", () => {}); + + describe("Performance Tests", () => {}); +}); diff --git a/src/test/slider/slider.test.ts b/src/test/slider/slider.test.ts new file mode 100644 index 0000000..123684c --- /dev/null +++ b/src/test/slider/slider.test.ts @@ -0,0 +1,160 @@ +import { fixture, html, unsafeStatic, expect } from "@open-wc/testing"; +import type { ZetaSlider } from "../../components/slider/slider.js"; +import "../../components/slider/slider.js"; +import "../../index.css"; +import { getCssVarColorValue } from "../utils.js"; + +describe("zeta-slider", () => { + let subject: ZetaSlider; + + const createComponent = (template = `<zeta-slider></zeta-slider>`) => { + // prettier-ignore + return fixture<ZetaSlider>(html`${unsafeStatic(template)}`); + }; + + beforeEach(async () => { + subject = await createComponent(); + await subject.updateComplete; + }); + + describe("Accessibility Tests", () => { + it("meets accessibility requirements", async () => { + await expect(subject).shadowDom.to.be.accessible(); + }); + }); + + describe("Content Tests", () => { + it("renders the track", () => { + const track = subject.shadowRoot?.querySelector(".track"); + expect(track).to.exist; + }); + + it("renders the left handle and not the right handle when type is default", () => { + const handle = subject.shadowRoot?.querySelector("#handle-l"); + expect(handle).to.exist; + + const handleR = subject.shadowRoot?.querySelector("#handle-r"); + expect(handleR).to.not.exist; + }); + + it("renders the right handle when type is range", async () => { + subject.type = "range"; + await subject.updateComplete; + const handle = subject.shadowRoot?.querySelector("#handle-r"); + expect(handle).to.exist; + }); + + it("sets initial values correctly", async () => { + await expect(subject.value).to.equal(50); + await expect(subject.lowerValue).to.equal(10); + await expect(subject.upperValue).to.equal(90); + await expect(subject.min).to.equal(0); + await expect(subject.max).to.equal(100); + await expect(subject.stepIncrement).to.equal(undefined); + await expect(subject.type).to.equal("default"); + }); + }); + + describe("Dimensions Tests", () => { + it("sets the track height correctly", async () => { + const track = subject.shadowRoot?.querySelector(".track"); + + await expect(track?.clientHeight).to.equal(4); + }); + + it("sets the handle width and height correctly", async () => { + const handle = subject.shadowRoot?.querySelector(".handle"); + await expect(handle?.clientHeight).to.equal(16); + await expect(handle?.clientHeight).to.equal(16); + }); + + it("sets the correct margin on the slider", async () => { + const track = subject.shadowRoot?.querySelector(".slider"); + await expect(getComputedStyle(track as Element).margin).to.equal("0px 8px"); + }); + }); + + describe("Styling Tests", () => { + it("sets the correct background color for the track", async () => { + const track = subject.shadowRoot?.querySelector(".track"); + await expect(getComputedStyle(track as Element).backgroundColor).to.equal(getCssVarColorValue(track!, "--surface-disabled")); + }); + + it("sets the correct color for the selected area", async () => { + const selected = subject.shadowRoot?.querySelector(".selected-area"); + await expect(getComputedStyle(selected as Element).backgroundColor).to.equal(getCssVarColorValue(selected!, "--main-default")); + }); + + it("sets the correct background color for the handle", async () => { + const handle = subject.shadowRoot?.querySelector(".handle"); + await expect(getComputedStyle(handle as Element).backgroundColor).to.equal(getCssVarColorValue(handle!, "--main-default")); + }); + + it("sets the correct color for the selected area and handle when pressed down", async () => { + const selected = subject.shadowRoot?.querySelector(".selected-area"); + const handle = subject.shadowRoot?.querySelector(".handle"); + + handle?.dispatchEvent(new MouseEvent("mousedown", { bubbles: true, composed: true })); + + await expect(getComputedStyle(selected as Element).backgroundColor).to.equal(getCssVarColorValue(selected!, "--main-primary")); + await expect(getComputedStyle(handle as Element).backgroundColor).to.equal(getCssVarColorValue(handle!, "--main-primary")); + + handle?.dispatchEvent(new MouseEvent("mouseup", { bubbles: true, composed: true })); + }); + + it("sets the correct border radius for the handle", async () => { + const handle = subject.shadowRoot?.querySelector(".handle"); + await expect(getComputedStyle(handle as Element).borderRadius).to.equal("0px"); + + subject.rounded = true; + await subject.updateComplete; + await expect(getComputedStyle(handle as Element).borderRadius).to.equal("360px"); + }); + + it("sets the correct border radius for the track", async () => { + const track = subject.shadowRoot?.querySelector(".track"); + await expect(getComputedStyle(track as Element).borderRadius).to.equal("0px"); + + subject.rounded = true; + await subject.updateComplete; + await expect(getComputedStyle(track as Element).borderRadius).to.equal("4px"); + }); + }); + + describe("Interaction Tests", () => { + it("updates the value when the handle is dragged", async () => { + const handle = subject.shadowRoot?.querySelector(".handle"); + handle?.dispatchEvent(new MouseEvent("mousedown", { bubbles: true, composed: true })); + handle?.dispatchEvent(new MouseEvent("mousemove", { bubbles: true, composed: true, clientX: 800 })); + handle?.dispatchEvent(new MouseEvent("mouseup", { bubbles: true, composed: true })); + + await expect(subject.value).to.equal(100); + }); + + it("updates the lower and upper values when the handles are dragged", async () => { + subject.type = "range"; + await subject.updateComplete; + + await expect(subject.lowerValue).to.equal(10); + await expect(subject.upperValue).to.equal(90); + + const handleL = subject.shadowRoot?.querySelector("#handle-l"); + const handleR = subject.shadowRoot?.querySelector("#handle-r"); + + handleL?.dispatchEvent(new MouseEvent("mousedown", { bubbles: true, composed: true })); + handleL?.dispatchEvent(new MouseEvent("mousemove", { bubbles: true, composed: true, clientX: 250 })); + handleL?.dispatchEvent(new MouseEvent("mouseup", { bubbles: true, composed: true })); + + handleR?.dispatchEvent(new MouseEvent("mousedown", { bubbles: true, composed: true })); + handleR?.dispatchEvent(new MouseEvent("mousemove", { bubbles: true, composed: true, clientX: 500 })); + handleR?.dispatchEvent(new MouseEvent("mouseup", { bubbles: true, composed: true })); + + expect(subject.lowerValue).to.be.closeTo(30, 5); + expect(subject.upperValue).to.be.closeTo(65, 5); + }); + }); + + describe("Golden Tests", () => {}); + + describe("Performance Tests", () => {}); +}); diff --git a/src/test/snackbar/snackbar.test.ts b/src/test/snackbar/snackbar.test.ts new file mode 100644 index 0000000..e343b91 --- /dev/null +++ b/src/test/snackbar/snackbar.test.ts @@ -0,0 +1,243 @@ +import { fixture, html, unsafeStatic, expect, waitUntil } from "@open-wc/testing"; +import type { ZetaSnackbar } from "../../components/snackbar/snackbar"; +import { getCssVarColorValue, getCssVarValue, getIconColor, MouseActions } from "../utils"; +import { sendKeys } from "@web/test-runner-commands"; +import "../../components/snackbar/snackbar.js"; +import type { ZetaIcon } from "../../components/icon/icon.js"; +import "../../components/icon/icon.js"; +import "../../index.css"; +import "@zebra-fed/zeta-icons/index.css"; + +describe("zeta-snackbar", () => { + let subject: ZetaSnackbar; + + const createComponent = ( + template = `<zeta-snackbar actionLabel="Action" status="default" hasCloseAction><zeta-icon slot="icon">happy</zeta-icon>Message</zeta-snackbar>` + ) => { + // prettier-ignore + return fixture<ZetaSnackbar>(html`${unsafeStatic(template)}`); + }; + + beforeEach(async () => { + subject = await createComponent(); + subject.actionClick = () => console.log("Action Clicked"); + }); + + describe("Accessibility Tests", () => { + // TODO: collaborate with design to fix contrast ratio of action button text in default status + it.skip("meets accessibility requirements", async () => { + await expect(subject).shadowDom.to.be.accessible(); + }); + }); + + describe("Content Tests", () => { + it("displays the correct text in the slot", async () => { + const slotContent = (subject.shadowRoot!.querySelector("slot:not([name='icon'])") as HTMLSlotElement)?.assignedNodes({ flatten: true }); + const textNode = slotContent.find(node => node.nodeType === Node.TEXT_NODE) as Text; + + await expect(textNode.textContent).to.equal("Message"); + }); + + it("displays the correct text in the action button", async () => { + const actionButton = subject.shadowRoot!.querySelector("button") as HTMLElement; + await expect(actionButton.textContent).to.equal("Action"); + }); + + it("displays the correct icon in the slot", async () => { + const iconSlotContent = (subject.shadowRoot!.querySelector("slot[name='icon']") as HTMLSlotElement)?.assignedNodes({ flatten: true }); + const iconElement = iconSlotContent.find(node => node.nodeType === Node.ELEMENT_NODE && (node as HTMLElement).tagName === "ZETA-ICON") as ZetaIcon; + + expect(iconElement).to.not.be.null; + await expect(iconElement.textContent).to.equal("happy"); + }); + }); + + // describe("Dimensions Tests", () => {}); + + describe("Styling Tests", () => { + beforeEach(async () => { + subject = await createComponent(); + subject.actionClick = () => console.log("Action Clicked"); + }); + it("sets full border radius correctly", async () => { + subject.round = "full"; + await subject.updateComplete; + await expect(subject.round).to.equal("full"); + await expect(getComputedStyle(subject).borderRadius).to.equal(getCssVarValue(subject, "--radius-full")); + }); + + it("sets true border radius correctly", async () => { + subject.round = true; + await subject.updateComplete; + expect(subject.round).to.be.true; + await expect(getComputedStyle(subject).borderRadius).to.equal(getCssVarValue(subject, "--radius-minimal")); + }); + + it("sets false border radius correctly", async () => { + subject.round = false; + await subject.updateComplete; + expect(subject.round).to.be.false; + await expect(getComputedStyle(subject).borderRadius).to.equal(getCssVarValue(subject, "--radius-none")); + }); + + it("sets the correct colors for default status", async () => { + const icon = subject.querySelector("zeta-icon") as ZetaIcon; + const closeIcon = subject.shadowRoot!.querySelector("#closeIcon") as ZetaIcon; + const actionButton = subject.shadowRoot!.querySelector("button") as HTMLElement; + + subject.status = "default"; + await subject.updateComplete; + await expect(subject.status).to.equal("default"); + await expect(getComputedStyle(subject).backgroundColor).to.equal(getCssVarColorValue(subject, "--surface-default-inverse")); + await expect(getComputedStyle(subject).color).to.equal(getCssVarColorValue(subject, "--main-inverse")); + await expect(getIconColor(icon)).to.equal(getCssVarColorValue(subject, "--main-inverse")); + await expect(getIconColor(closeIcon)).to.equal(getCssVarColorValue(subject, "--main-inverse")); + await expect(getComputedStyle(actionButton).color).to.equal(getCssVarColorValue(subject, "--main-primary")); + }); + + it("sets the correct colors for positive status", async () => { + const icon = subject.querySelector("zeta-icon") as ZetaIcon; + const closeIcon = subject.shadowRoot!.querySelector("#closeIcon") as ZetaIcon; + const actionButton = subject.shadowRoot!.querySelector("button") as HTMLElement; + + subject.status = "positive"; + await subject.updateComplete; + await expect(subject.status).to.equal("positive"); + await expect(getComputedStyle(subject).backgroundColor).to.equal(getCssVarColorValue(subject, "--surface-positive-subtle")); + await expect(getComputedStyle(subject).color).to.equal(getCssVarColorValue(subject, "--main-default")); + await expect(getIconColor(icon)).to.equal(getCssVarColorValue(subject, "--main-positive")); + await expect(getIconColor(closeIcon)).to.equal(getCssVarColorValue(subject, "--main-default")); + await expect(getComputedStyle(actionButton).color).to.equal(getCssVarColorValue(subject, "--main-default")); + }); + + it("sets the correct colors for info status", async () => { + const icon = subject.querySelector("zeta-icon") as ZetaIcon; + const closeIcon = subject.shadowRoot!.querySelector("#closeIcon") as ZetaIcon; + const actionButton = subject.shadowRoot!.querySelector("button") as HTMLElement; + + subject.status = "info"; + await subject.updateComplete; + await expect(subject.status).to.equal("info"); + await expect(getComputedStyle(subject).backgroundColor).to.equal(getCssVarColorValue(subject, "--surface-info-subtle")); + await expect(getComputedStyle(subject).color).to.equal(getCssVarColorValue(subject, "--main-default")); + await expect(getIconColor(icon)).to.equal(getCssVarColorValue(subject, "--main-info")); + await expect(getIconColor(closeIcon)).to.equal(getCssVarColorValue(subject, "--main-default")); + await expect(getComputedStyle(actionButton).color).to.equal(getCssVarColorValue(subject, "--main-default")); + }); + + it("sets the correct colors for warning status", async () => { + const icon = subject.querySelector("zeta-icon") as ZetaIcon; + const closeIcon = subject.shadowRoot!.querySelector("#closeIcon") as ZetaIcon; + const actionButton = subject.shadowRoot!.querySelector("button") as HTMLElement; + + subject.status = "warning"; + await subject.updateComplete; + await expect(subject.status).to.equal("warning"); + await expect(getComputedStyle(subject).backgroundColor).to.equal(getCssVarColorValue(subject, "--surface-warning-subtle")); + await expect(getComputedStyle(subject).color).to.equal(getCssVarColorValue(subject, "--main-default")); + await expect(getIconColor(icon)).to.equal(getCssVarColorValue(subject, "--main-warning")); + await expect(getIconColor(closeIcon)).to.equal(getCssVarColorValue(subject, "--main-default")); + await expect(getComputedStyle(actionButton).color).to.equal(getCssVarColorValue(subject, "--main-default")); + }); + + it("sets the correct colors for negative status", async () => { + const icon = subject.querySelector("zeta-icon") as ZetaIcon; + const closeIcon = subject.shadowRoot!.querySelector("#closeIcon") as ZetaIcon; + const actionButton = subject.shadowRoot!.querySelector("button") as HTMLElement; + + subject.status = "negative"; + await subject.updateComplete; + await expect(subject.status).to.equal("negative"); + await expect(getComputedStyle(subject).backgroundColor).to.equal(getCssVarColorValue(subject, "--surface-negative-subtle")); + await expect(getComputedStyle(subject).color).to.equal(getCssVarColorValue(subject, "--main-default")); + await expect(getIconColor(icon)).to.equal(getCssVarColorValue(subject, "--main-negative")); + await expect(getIconColor(closeIcon)).to.equal(getCssVarColorValue(subject, "--main-default")); + await expect(getComputedStyle(actionButton).color).to.equal(getCssVarColorValue(subject, "--main-default")); + }); + + it("sets the correct colors for view status", async () => { + const icon = subject.querySelector("zeta-icon") as ZetaIcon; + const closeIcon = subject.shadowRoot!.querySelector("#closeIcon") as ZetaIcon; + const actionButton = subject.shadowRoot!.querySelector("button") as HTMLElement; + + subject.status = "view"; + await subject.updateComplete; + await expect(subject.status).to.equal("view"); + await expect(getComputedStyle(subject).backgroundColor).to.equal(getCssVarColorValue(subject, "--surface-primary-subtle")); + await expect(getComputedStyle(subject).color).to.equal(getCssVarColorValue(subject, "--main-default")); + await expect(getIconColor(icon)).to.equal(getCssVarColorValue(subject, "--main-primary")); + await expect(getIconColor(closeIcon)).to.equal(getCssVarColorValue(subject, "--main-default")); + await expect(getComputedStyle(actionButton).color).to.equal(getCssVarColorValue(subject, "--main-default")); + }); + }); + + describe("Interaction Tests", () => { + it("removes the element from the screen when the close icon is clicked", async () => { + const closeIcon = subject.shadowRoot!.querySelector("#closeIcon") as ZetaIcon; + await MouseActions.click(closeIcon, "left"); + await subject.updateComplete; + + const snackbar = document.querySelector("zeta-snackbar"); + expect(snackbar).to.not.exist; + }); + + it("removes the element from the screen when the close icon is enter pressed", async () => { + await sendKeys({ press: "Tab" }); + await sendKeys({ press: "Tab" }); + await sendKeys({ press: "Enter" }); + await subject.updateComplete; + + const snackbar = document.querySelector("zeta-snackbar"); + expect(snackbar).to.not.exist; + }); + + it("removes the element from the screen when the close icon is space pressed", async () => { + await sendKeys({ press: "Tab" }); + await sendKeys({ press: "Tab" }); + await sendKeys({ press: " " }); + await subject.updateComplete; + + const snackbar = document.querySelector("zeta-snackbar"); + expect(snackbar).to.not.exist; + }); + + it("calls the action function on enter pressed", async () => { + let clicked = false; + + subject.actionClick = () => { + console.log("Action Clicked"); + clicked = true; + }; + await sendKeys({ press: "Tab" }); + void sendKeys({ press: "Enter" }); + await waitUntil(() => clicked, "Button Action fired", { timeout: 200 }); + }); + + it("calls the action function on space pressed", async () => { + let clicked = false; + + subject.actionClick = () => { + console.log("Action Clicked"); + clicked = true; + }; + await sendKeys({ press: "Tab" }); + void sendKeys({ press: " " }); + await waitUntil(() => clicked, "Button Action fired", { timeout: 100 }); + }); + + it("calls the action function on mouse click", async () => { + let clicked = false; + + subject.actionClick = () => { + console.log("Action Clicked"); + clicked = true; + }; + await MouseActions.click(subject.shadowRoot!.querySelector("#action") as HTMLElement, "left"); + await waitUntil(() => clicked, "Button Action fired", { timeout: 100 }); + }); + }); + + // describe("Golden Tests", () => {}); + + // describe("Performance Tests", () => {}); +}); diff --git a/src/test/stepper-input/stepper-input.test.ts b/src/test/stepper-input/stepper-input.test.ts new file mode 100644 index 0000000..2511a5a --- /dev/null +++ b/src/test/stepper-input/stepper-input.test.ts @@ -0,0 +1,81 @@ +import { assert, fixture, html } from "@open-wc/testing"; +import { ZetaStepperInput } from "../../components/stepper-input/stepper-input.js"; +import "../../components/stepper-input/stepper-input.js"; + +describe("zeta-stepper-input", () => { + // describe("Accessibility Tests", () => {}); + + describe("Content Tests", () => { + it("creates from document.createElement", function () { + const el = document.createElement("zeta-stepper-input"); + assert.equal("ZETA-STEPPER-INPUT", el.nodeName); + }); + + it("creates from constructor", function () { + const el = new ZetaStepperInput(); + assert.equal("ZETA-STEPPER-INPUT", el.nodeName); + }); + + it("sets value", async () => { + // prettier-ignore + const el = await fixture<ZetaStepperInput>(html` <zeta-stepper-input value=${10}></zeta-stepper-input>`); + assert.equal(el.value, 10); + }); + + it("sets value to max value", async () => { + // prettier-ignore + const el = await fixture<ZetaStepperInput>(html` <zeta-stepper-input max=${9} value=${10}></zeta-stepper-input>`); + assert.equal(el.value, 9); + }); + + it("sets value to min value", async () => { + // prettier-ignore + const el = await fixture<ZetaStepperInput>(html` <zeta-stepper-input min=${9} value=${8}></zeta-stepper-input>`); + assert.equal(el.value, 9); + }); + + it("doesn't change value", async () => { + // prettier-ignore + const el = await fixture<ZetaStepperInput>(html` <zeta-stepper-input></zeta-stepper-input>`); + el.value = Number("123asd"); + assert.equal(el.value, 0); + }); + + it("doesn't change value", async () => { + // prettier-ignore + const el = await fixture<ZetaStepperInput>(html` <zeta-stepper-input></zeta-stepper-input>`); + const input = el.shadowRoot?.querySelector("input"); + input!.value = "test"; + input?.dispatchEvent(new Event("change", { bubbles: true })); + assert.equal(el.value, 0); + }); + + it("sets value to min value via input onchange", async () => { + // prettier-ignore + const el = await fixture<ZetaStepperInput>(html` <zeta-stepper-input min=${5}></zeta-stepper-input>`); + const input = el.shadowRoot?.querySelector("input"); + input!.value = "4"; + input?.dispatchEvent(new Event("change", { bubbles: true })); + assert.equal(el.value, 5); + }); + + it("sets value to max value via input onchange", async () => { + // prettier-ignore + const el = await fixture<ZetaStepperInput>(html` <zeta-stepper-input max=${5}></zeta-stepper-input>`); + const input = el.shadowRoot?.querySelector("input"); + input!.value = "6"; + input?.dispatchEvent(new Event("change", { bubbles: true })); + assert.equal(el.value, 5); + }); + }); + + // describe("Dimensions Tests", () => {}); + + // describe("Styling Tests", () => {}); + + // describe("Interaction Tests", () => {}); + + // describe("Golden Tests", () => {}); + + // describe("Performance Tests", () => {}); +}); diff --git a/src/test/stepper-input/stepper.test.ts b/src/test/stepper-input/stepper.test.ts new file mode 100644 index 0000000..5ca9661 --- /dev/null +++ b/src/test/stepper-input/stepper.test.ts @@ -0,0 +1,42 @@ +import { assert, fixture, html } from "@open-wc/testing"; +import { ZetaStepper } from "../../components/stepper/stepper.js"; +import "../../components/stepper/stepper.js"; + +describe("zeta-stepper", () => { + // describe("Accessibility Tests", () => {}); + + describe("Content Tests", () => { + it("creates from document.createElement", function () { + const el = document.createElement("zeta-stepper"); + assert.equal("ZETA-STEPPER", el.nodeName); + }); + + it("creates from constructor", function () { + const el = new ZetaStepper(); + assert.equal("ZETA-STEPPER", el.nodeName); + }); + + it("should render steps", async () => { + const el = await fixture(html` + <zeta-stepper> + <li data-title="title 1" data-label="label 1"></li> + <li data-title="title 2" data-label="label 2"></li> + <li data-title="title 3" data-label="label 3"></li> + </zeta-stepper> + `); + + const items = el.shadowRoot?.querySelectorAll(".step-container"); + assert.equal(items?.length, 3); + }); + }); + + // describe("Dimensions Tests", () => {}); + + // describe("Styling Tests", () => {}); + + // describe("Interaction Tests", () => {}); + + // describe("Golden Tests", () => {}); + + // describe("Performance Tests", () => {}); +}); diff --git a/src/test/switch/switch.test.ts b/src/test/switch/switch.test.ts new file mode 100644 index 0000000..c4aaf8f --- /dev/null +++ b/src/test/switch/switch.test.ts @@ -0,0 +1,154 @@ +import { fixture, html, expect, assert } from "@open-wc/testing"; +import { getIconColor, MouseActions, getCssVarColorValue } from "../utils.js"; +import type { ZetaIcon } from "../../components/icon/icon.js"; +import { ZetaSwitch } from "../../components/switch/switch.js"; +import "../../components/switch/switch.js"; +import "../../index.css"; + +describe("zeta-switch", () => { + let subject: ZetaSwitch; + beforeEach(async () => { + subject = await fixture(html`<zeta-switch activeIcon="microphone" inactiveIcon="microphone_off"></zeta-switch>`); + }); + + // describe("Accessibility Tests", () => {}); + + describe("Content Tests", () => { + it("creates from document.createElement", function () { + const el = document.createElement("zeta-switch"); + assert.equal("ZETA-SWITCH", el.nodeName); + }); + + it("creates from constructor", function () { + const el = new ZetaSwitch(); + assert.equal("ZETA-SWITCH", el.nodeName); + }); + + it("focus on button when switch focused", () => { + subject.focus(); + expect(subject.shadowRoot?.querySelector(":focus")).to.exist; + }); + + it("blur on button when switch blurred", () => { + subject.focus(); + subject.blur(); + + expect(subject.shadowRoot?.querySelector("button:focus")).not.to.exist; + }); + }); + + // describe("Dimensions Tests", () => {}); + + describe("Styling Tests", () => { + let subject: ZetaSwitch; + let track: HTMLElement | null | undefined; + let thumb: HTMLElement | null | undefined; + let activeIcon: ZetaIcon | null | undefined; + let inactiveIcon: ZetaIcon | null | undefined; + + beforeEach(async () => { + subject = await fixture(html`<zeta-switch activeIcon="microphone" inactiveIcon="microphone_off"></zeta-switch>`); + track = subject.shadowRoot?.querySelector('[part="track"]'); + thumb = subject.shadowRoot?.querySelector('[part="thumb"]'); + activeIcon = subject.shadowRoot?.querySelector('zeta-icon[part="icon active"]'); + inactiveIcon = subject.shadowRoot?.querySelector('zeta-icon[part="icon inactive"]'); + }); + it("activeIcon color", async () => { + await expect(getComputedStyle(activeIcon!).color).to.equal(getCssVarColorValue(activeIcon!, "--main-inverse")); + }); + it("inactiveIcon color", async () => { + await expect(getComputedStyle(inactiveIcon!).color).to.equal(getCssVarColorValue(inactiveIcon!, "--main-inverse")); + }); + it("track backgroundColor", async () => { + await expect(getComputedStyle(track!).backgroundColor).to.equal(getCssVarColorValue(track!, "--main-disabled")); + }); + it("thumb backgroundColor", async () => { + await expect(getComputedStyle(thumb!).backgroundColor).to.equal(getCssVarColorValue(thumb!, "--main-inverse")); + }); + }); + + describe("Interaction Tests", () => { + it("should toggle the state when clicked by accessibility tools", async () => { + const t: ZetaSwitch = await fixture(html`<zeta-switch></zeta-switch>`); + const input = t.shadowRoot?.querySelector("input") as HTMLInputElement; + + input?.click(); + expect(input.checked).to.be.true; + expect(t.checked).to.be.true; + + input?.click(); + expect(input.checked).to.be.false; + expect(t.checked).to.be.false; + }); + }); + + // describe("Golden Tests", () => {}); + + // describe("Performance Tests", () => {}); +}); + +describe("zeta-switch disabled", () => { + describe("Styling Tests", () => { + let subject: ZetaSwitch; + let track: HTMLElement | null | undefined; + let thumb: HTMLElement | null | undefined; + let activeIcon: ZetaIcon | null | undefined; + let inactiveIcon: ZetaIcon | null | undefined; + + beforeEach(async () => { + // await emulateMedia({ reducedMotion: "reduce" }); + subject = await fixture(html`<zeta-switch activeIcon="microphone" inactiveIcon="microphone_off" disabled></zeta-switch>`); + track = subject.shadowRoot?.querySelector('[part="track"]'); + thumb = subject.shadowRoot?.querySelector('[part="thumb"]'); + activeIcon = subject.shadowRoot?.querySelector('zeta-icon[part="icon active"]') as ZetaIcon; + inactiveIcon = subject.shadowRoot?.querySelector('zeta-icon[part="icon inactive"]') as ZetaIcon; + }); + it("inactiveIcon color, :hover color", async () => { + await expect(getIconColor(inactiveIcon!)).to.equal(getCssVarColorValue(inactiveIcon!, "--main-disabled")); + await MouseActions.hover(subject); + await expect(getIconColor(inactiveIcon!)).to.equal(getCssVarColorValue(inactiveIcon!, "--main-disabled")); + }); + it("activeIcon color, :hover color", async () => { + await expect(getIconColor(activeIcon!)).to.equal(getCssVarColorValue(activeIcon!, "--main-disabled")); + await MouseActions.hover(subject); + await expect(getIconColor(activeIcon!)).to.equal(getCssVarColorValue(activeIcon!, "--main-disabled")); + }); + it("track backgroundColor, :hover backgroundColor", async () => { + await expect(getComputedStyle(track!).backgroundColor).to.equal(getCssVarColorValue(track!, "--surface-disabled")); + await MouseActions.hover(subject); + await expect(getComputedStyle(track!).backgroundColor).to.equal(getCssVarColorValue(track!, "--surface-disabled")); + }); + it("thumb backgroundColor, :hover backgroundColor", async () => { + await expect(getComputedStyle(thumb!).backgroundColor).to.equal(getCssVarColorValue(thumb!, "--main-disabled")); + await MouseActions.hover(subject); + await expect(getComputedStyle(thumb!).backgroundColor).to.equal(getCssVarColorValue(thumb!, "--main-disabled")); + }); + }); +}); + +describe("zeta-switch:hover", () => { + describe("Styling Tests", () => { + let subject: ZetaSwitch; + let track: HTMLElement | null | undefined; + let thumb: HTMLElement | null | undefined; + + beforeEach(async () => { + subject = await fixture(html`<zeta-switch activeIcon="microphone" inactiveIcon="microphone_off"></zeta-switch>`); + track = subject.shadowRoot?.querySelector('[part="track"]'); + thumb = subject.shadowRoot?.querySelector('[part="thumb"]'); + }); + + it("track backgroundColor", async () => { + await MouseActions.hover(subject); + await expect(getComputedStyle(track!).backgroundColor).to.equal(getCssVarColorValue(track!, "--main-disabled")); + await MouseActions.reset(); + await expect(getComputedStyle(track!).backgroundColor).to.equal(getCssVarColorValue(track!, "--main-disabled")); + }); + it("thumb backgroundColor", async () => { + await MouseActions.hover(subject); + await expect(getComputedStyle(thumb!).backgroundColor).to.equal(getCssVarColorValue(track!, "--main-inverse")); + await MouseActions.reset(); + await expect(getComputedStyle(thumb!).backgroundColor).to.equal(getCssVarColorValue(track!, "--main-inverse")); + }); + }); +}); diff --git a/src/test/tag/tag.test.ts b/src/test/tag/tag.test.ts new file mode 100644 index 0000000..0c09202 --- /dev/null +++ b/src/test/tag/tag.test.ts @@ -0,0 +1,67 @@ +import { fixture, html, elementUpdated, expect, unsafeStatic } from "@open-wc/testing"; +import type { ZetaTag } from "../../components/badges/tag/tag.js"; +import "../../components/badges/tag/tag.js"; + +describe("zeta-tag", () => { + let subject: ZetaTag; + + const createComponent = (template = "<zeta-tag></zeta-tag>") => { + // prettier-ignore + return fixture<ZetaTag>(html`${unsafeStatic(template)}`); + }; + + beforeEach(async () => { + subject = await createComponent(); + }); + + describe("Accessibility Tests", () => { + it("it meets accessibility requirements", async () => { + await expect(subject).shadowDom.to.be.accessible(); + }); + }); + + describe("Content Tests", () => { + it("sets the default properties correctly", async () => { + await expect(subject.point).to.equal("right"); + await expect(subject.text).to.equal(""); + }); + + it("manages point attribute correctly", async () => { + let pointValue = "left"; + + subject.setAttribute("point", pointValue); + await expect(subject.point).to.equal(pointValue); + + pointValue = "right"; + + subject.setAttribute("point", pointValue); + await expect(subject.point).to.equal(pointValue); + }); + + it("manages text attribute correctly", async () => { + const textValue = "Testing service"; + subject.setAttribute("text", textValue); + await expect(subject.text).to.equal(textValue); + }); + + it("renders the passed text into a span", async () => { + const textValue = "Testing service"; + subject.setAttribute("text", textValue); + await elementUpdated(subject); + + const spanEl = subject.shadowRoot ? (subject.shadowRoot.querySelector("span") as HTMLSpanElement) : (subject.querySelector("span") as HTMLSpanElement); + + await expect(spanEl.textContent).to.equal(textValue); + }); + }); + + // describe("Dimensions Tests", () => {}); + + // describe("Styling Tests", () => {}); + + // describe("Interaction Tests", () => {}); + + // describe("Golden Tests", () => {}); + + // describe("Performance Tests", () => {}); +}); diff --git a/src/test/text-input/setup.ts b/src/test/text-input/setup.ts new file mode 100644 index 0000000..5559a88 --- /dev/null +++ b/src/test/text-input/setup.ts @@ -0,0 +1,55 @@ +import { fixture, html } from "@open-wc/testing"; +import type { ZetaIconName } from "@zebra-fed/zeta-icons"; +import type { ZetaTextInput } from "../../components/text-input/text-input.js"; +import "../../components/text-input/text-input.js"; +import { ifDefined } from "lit/directives/if-defined.js"; + +interface Props { + disabled?: boolean; + leadingIcon?: ZetaIconName; + trailingIcon?: ZetaIconName; + suffix?: string; + prefix?: string; + error?: boolean; + required?: boolean; + label?: string; + hint?: string; + errorText?: string; + name?: string; + value?: string; + type?: "text" | "date" | "textarea" | "password" | "time"; +} + +export async function setup({ + hint = "", + label = "", + disabled = false, + error = false, + leadingIcon, + trailingIcon, + prefix = "", + required = false, + suffix = "", + type = "text", + errorText = undefined, + name = undefined, + value = undefined +}: Props) { + return await fixture<ZetaTextInput>( + html`<zeta-text-input + ?required=${required} + ?error=${error} + hintText=${hint} + suffix=${suffix} + prefix=${prefix} + ?disabled=${disabled} + leadingIcon=${ifDefined(leadingIcon)} + trailingIcon=${ifDefined(trailingIcon)} + errorText=${errorText ?? ""} + type=${type} + name=${ifDefined(name)} + value=${ifDefined(value)} + label=${ifDefined(label)} + ></zeta-text-input>` + ); +} diff --git a/src/test/text-input/text-input.test.ts b/src/test/text-input/text-input.test.ts new file mode 100644 index 0000000..444a512 --- /dev/null +++ b/src/test/text-input/text-input.test.ts @@ -0,0 +1,176 @@ +import { expect, assert } from "@open-wc/testing"; +import { setup } from "./setup.js"; +import type { ZetaIcon } from "../../components/icon/icon.js"; +import { ZetaTextInput } from "../../components/text-input/text-input.js"; +import { getSlotText, getCssVarColorValue } from "../utils.js"; +import "../../components/text-input/text-input.js"; +import "../../index.css"; + +describe("zeta-text-input", () => { + // describe("Accessibility Tests", () => {}); + + describe("Content Tests", () => { + it.skip("creates from document.createElement", function () { + const el = document.createElement("zeta-text-input"); + assert.equal("ZETA-TEXT-INPUT", el.nodeName); + }); + + it("creates from constructor", function () { + const el = new ZetaTextInput(); + assert.equal("ZETA-TEXT-INPUT", el.nodeName); + }); + + it("focus on input when field focused", async () => { + const el = await setup({}); + el.focus(); + return expect(el.shadowRoot?.querySelector("input:focus")).to.exist; + }); + + it("should not focus on input when field disabled", async () => { + const el = await setup({ disabled: true }); + el.focus(); + return expect(el.shadowRoot?.querySelector("input:focus")).not.to.exist; + }); + + it("blur on input when field blurred", async () => { + const el = await setup({}); + el.focus(); + el.blur(); + return expect(el.shadowRoot?.querySelector("input:focus")).not.to.exist; + }); + + it("should render icon", async () => { + const el = await setup({ leadingIcon: "star" }); + assert.equal(el.shadowRoot?.querySelector("zeta-icon")?.textContent?.trim(), "star"); + assert.equal(el.shadowRoot?.querySelector("zeta-icon")?.getAttribute("class")!.includes("left"), true); + }); + + it("should render icon on the right", async () => { + const el = await setup({ trailingIcon: "star" }); + assert.equal(el.shadowRoot?.querySelector("zeta-icon")?.textContent?.trim(), "star"); + assert.equal(el.shadowRoot?.querySelector("zeta-icon")?.getAttribute("class")!.includes("right"), true); + }); + + it("should render prefix", async () => { + const el = await setup({ prefix: "PREFIX" }); + assert.equal(el.shadowRoot?.querySelector(".left.affix")?.textContent, "PREFIX"); + }); + + it("should render suffix", async () => { + const t = await setup({ suffix: "SUFFIX" }); + assert.equal(t.shadowRoot?.querySelector(".right.affix")?.textContent, "SUFFIX"); + }); + + it("should render label", async () => { + const labelText = "Label"; + const t = await setup({ label: labelText }); + const label = t.shadowRoot?.querySelector("label"); + + assert.notEqual(label, undefined); + assert.equal(label?.textContent?.includes(labelText), true); + }); + + it("should render hint text", async () => { + const el = await setup({ hint: "hint" }); + assert.equal(el.shadowRoot?.querySelector(".hint-text span")?.textContent, "hint"); + }); + + it("should render error icon", async () => { + const el = await setup({ error: true, hint: "hint", disabled: false, errorText: "error" }); + const icon = el.shadowRoot?.querySelector(".hint-text zeta-icon") as ZetaIcon; + assert.equal(getSlotText(icon), "error"); + }); + + it("should render error text", async () => { + const el = await setup({ error: true, hint: "hint", disabled: false, errorText: "errory" }); + assert.equal(el.shadowRoot?.querySelector(".hint-text span")?.textContent, "errory"); + }); + + it("should change value", async () => { + const el = await setup({}); + const input = el.shadowRoot?.querySelector("input"); + input!.value = "change"; + input?.dispatchEvent(new Event("change", { bubbles: true })); + return assert.equal(el.value, "change"); + }); + + it("should apply type textarea", async () => { + const el = await setup({ type: "textarea" }); + const textarea = el.shadowRoot?.querySelector("textarea"); + return expect(textarea).not.to.be.null; + }); + + it("should apply type password", async () => { + const el = await setup({ type: "password" }); + const input = el.shadowRoot?.querySelector("input") as HTMLInputElement; + return assert.equal(input?.type, "password"); + }); + + it("should apply type time", async () => { + const el = await setup({ type: "time" }); + assert.equal(el.shadowRoot?.querySelector("zeta-icon")?.textContent?.trim(), "clock_outline"); + }); + + it("should apply type data", async () => { + const el = await setup({ type: "date" }); + assert.equal(el.shadowRoot?.querySelector("zeta-icon")?.textContent?.trim(), "calendar_3_day"); + }); + }); + + // describe("Dimensions Tests", () => {}); + + describe("Styling Tests", () => { + it("should render error icon color", async () => { + const el = await setup({ error: true, hint: "hint", disabled: false, errorText: "error" }); + const icon = el.shadowRoot?.querySelector(".hint-text zeta-icon"); + assert.equal(getComputedStyle(icon!).color, getCssVarColorValue(icon!, "--main-negative")); + }); + + it("should render error text color", async () => { + const el = await setup({ error: true, hint: "hint", disabled: false, errorText: "error" }); + const text = el.shadowRoot?.querySelector(".hint-text span"); + assert.equal(getComputedStyle(text!).color, getCssVarColorValue(text!, "--main-negative")); + }); + + const rgbToHex = (r: number, g: number, b: number) => + "#" + + [r, g, b] + .map(x => { + const hex = x.toString(16); + return hex.length === 1 ? "0" + hex : hex; + }) + .join(""); + + it("should set disabled color to icon when field is disabled", async () => { + const el = await setup({ disabled: true, leadingIcon: "star" }); + const x = el.shadowRoot?.querySelector("zeta-icon"); + expect(x).to.exist; + assert.equal(x!.textContent?.trim(), "star"); + + const rgbColor = window.getComputedStyle(x!).color.split("(")[1].split(")")[0].split(","); + const hexColor = rgbToHex(Number.parseInt(rgbColor[0]), Number.parseInt(rgbColor[1]), Number.parseInt(rgbColor[2])); + + return await expect(hexColor).to.equal(getComputedStyle(el).getPropertyValue("--main-disabled")); + }); + }); + + describe("Interaction Tests", () => { + it("should toggle password visibility", async () => { + const el = await setup({ type: "password" }); + const input = el.shadowRoot?.querySelector("input"); + input!.value = "password"; + input?.dispatchEvent(new Event("change", { bubbles: true })); + const icon = el.shadowRoot?.querySelector("zeta-icon") as ZetaIcon; + icon.click(); + await el.updateComplete; + assert.equal(el.type, "text"); + assert.equal(el.value, "password"); + }); + }); + + // describe("Golden Tests", () => {}); + + // describe("Performance Tests", () => {}); + + //TODO autocomplete, spellcheck, autofocus, aria-describedby, aria-label, placeholder +}); diff --git a/src/test/utils.ts b/src/test/utils.ts new file mode 100644 index 0000000..fccf521 --- /dev/null +++ b/src/test/utils.ts @@ -0,0 +1,148 @@ +import type { ZetaIcon } from "../components/icon/icon"; +import { sendMouse, resetMouse } from "@web/test-runner-commands"; + +/** + * Converts a color hex string to its RGB representation. + * @param color - The color string to convert. + * @returns The RGB representation of the color. + */ +export const toRGB = (color: string) => { + const { style } = new Option(); + style.color = color; + return style.color; +}; + +/** + * Retrieves the value of a CSS variable from an element. + * @param el - The element to get the CSS variable value from. + * @param cssVarName - The name of the CSS variable. + * @returns The value of the CSS variable. + */ +export const getCssVarValue = (el: Element, cssVarName: string): string => { + return getComputedStyle(el).getPropertyValue(cssVarName); +}; + +/** + * Retrieves the RGB representation of a CSS variable color from an element. + * @param el - The element to get the CSS variable color from. + * @param cssVarName - The name of the CSS variable. + * @returns The RGB representation of the CSS variable color. + */ +export const getCssVarColorValue = (el: Element, cssVarName: string): string => { + return toRGB(getCssVarValue(el, cssVarName)); +}; + +/** + * Retrieves the text node of an icon element, for an icon this is the part that holds the iconName + * @param elementWithSlot - The element that has a slot. + * @returns The text node of the element that has a slot. + */ +export const getSlotTextNode = (elementWithSlot: HTMLElement, slotName: string = ""): HTMLSlotElement | undefined => { + const parentSlot = [...elementWithSlot.shadowRoot!.querySelectorAll("slot")].find((a: HTMLSlotElement) => a.name === slotName); + const nodes = parentSlot?.assignedNodes(); + const slot = nodes?.find(a => a.nodeName === "SLOT" && (a as HTMLSlotElement).name === slotName) as HTMLSlotElement; + if (slot) { + return slot; + } else { + return parentSlot; + } +}; + +export const getSlot = (parent: HTMLElement, slotName: string = ""): HTMLSlotElement => { + return parent.shadowRoot?.querySelector(`slot[name="${slotName}"]`) as HTMLSlotElement; +}; + +/** + * Finds the icon in the named slot and returns its name (its text content). + * @param parent The parent element + * @param slotName (optional) the name of the slot, defaults to "icon" + * @returns the name of the icon in the slot, or null if not found + */ +export const getSlottedIconName = (parent: HTMLElement, slotName: string = "icon"): string | null => { + const slotElement = getSlot(parent, slotName); + const iconElement = slotElement.assignedElements().find(a => a.nodeName === "ZETA-ICON") as ZetaIcon; + return getSlotText(iconElement); +}; + +/** + * Retrieves the trimmed text of an element with a slot. + * @param elementWithASlot - The element with the slot. + * @returns The trimmed text of the elements slot. + */ +export const getSlotText = (elementWithASlot: HTMLElement): string | null => { + const slotNode = getSlotTextNode(elementWithASlot)!; + const textNodes = slotNode.assignedNodes(); + return textNodes + .filter(a => a.nodeName === "#text") + .reduce((acc, item) => acc + item.textContent, "") + .trim(); +}; + +/** + * Retrieves the color of an icon element in RGB format. + * @param icon - The icon element. + * @returns The color of the icon element. + */ +export const getIconColor = (icon: ZetaIcon): string => { + return getComputedStyle(getSlotTextNode(icon)!).color; +}; + +/** + * Retrieves the middle coordinates of an element, relative to the viewport. + * @param element - The element. + * @returns The middle coordinates of the element in x, y object format + */ +export const getMiddleOfElement = (element: HTMLElement): { x: number; y: number } => { + const { x, y, width, height } = element.getBoundingClientRect(); + + return { + x: Math.floor(x + window.pageXOffset + width / 2), + y: Math.floor(y + window.pageYOffset + height / 2) + }; +}; + +/** + * Provides mouse actions such as hover, click, and reset. + */ +export class MouseActions { + /** + * Moves the mouse to the center of the specified element. + * @param element - The element to hover over. + * @returns A promise that resolves when the mouse movement is complete. + */ + static hover = async (element: HTMLElement): Promise<void> => { + const { x, y } = getMiddleOfElement(element); + + return await sendMouse({ type: "move", position: [x, y] }); + }; + + /** + * Moves the mouse to the specified element and presses the specified mouse button. + * @param element - The element to click. + * @param button - The mouse button to press (default: "left"). + * @returns A promise that resolves when the mouse button is pressed. + */ + static down = async (element: HTMLElement, button: "left" | "right" | "middle" = "left"): Promise<void> => { + await this.hover(element); + return await sendMouse({ type: "down", button }); + }; + + /** + * Lifts the specified mouse button without moving the mouse. + * @param button - The mouse button to lift (default: "left"). + * @returns A promise that resolves when the mouse button is lifted. + */ + static up = async (button: "left" | "right" | "middle" = "left"): Promise<void> => { + return await sendMouse({ type: "up", button }); + }; + + static click = async (element: HTMLElement, mouseButton: "left" | "right" | "middle" = "left") => { + await this.down(element, mouseButton); + return await this.up(mouseButton); + }; + + /** + * Resets the mouse position to the top left corner of the viewport + */ + static reset = resetMouse; +} diff --git a/tsconfig.build.json b/tsconfig.build.json new file mode 100644 index 0000000..d4a18de --- /dev/null +++ b/tsconfig.build.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.json", + "exclude": ["**/generated", "**/stories", "**/test"], + "include": ["src/**/*.ts", "src/**/*.js"], + "compilerOptions": { + "allowJs": true, + "emitDeclarationOnly": false, + "outDir": "./dist" + }, +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..0c33352 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,47 @@ +{ + "compilerOptions": { + "allowJs": false, + "allowSyntheticDefaultImports": true, + "allowUnreachableCode": false, + "declaration": true, + "declarationMap": true, + "emitDeclarationOnly": true, + "esModuleInterop": true, + "experimentalDecorators": true, + "forceConsistentCasingInFileNames": true, + "isolatedModules": true, + "inlineSources": true, + "jsx": "react", + "lib": ["dom", "dom.iterable", "esnext"], + "module": "es2022", + "moduleResolution": "bundler", + "noUnusedLocals": true, + "noUnusedParameters": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "types": ["vite/client", "jest", "node"], + "noImplicitAny": true, + "noImplicitThis": true, + "plugins": [ + { + "name": "ts-lit-plugin", + "strict": true, + "rules": { + "no-incompatible-type-binding": "warning" + } + } + ], + "resolveJsonModule": true, + "rootDir": "./src", + "skipLibCheck": true, + "sourceMap": true, + "strict": true, + "strictNullChecks": true, + "strictPropertyInitialization": false, + "target": "ES2022", + "useDefineForClassFields": false, + "verbatimModuleSyntax": true, + }, + "include": ["src/**/*.ts"], + "exclude": ["**/generated"] +} diff --git a/typedoc.json b/typedoc.json new file mode 100644 index 0000000..8e12ee4 --- /dev/null +++ b/typedoc.json @@ -0,0 +1,7 @@ +{ + "groupOrder": ["Variables",], + "$schema": "https://typedoc.org/schema.json", + "entryPoints": ["./src/index.ts"], + "out": "docs", + "exclude":["*.stories.ts", "*.mdx", "*.test.ts", "*.md", "**/node_modules/**"] +} \ No newline at end of file diff --git a/web-test-runner.config.js b/web-test-runner.config.js new file mode 100644 index 0000000..81f008d --- /dev/null +++ b/web-test-runner.config.js @@ -0,0 +1,36 @@ +import { vitePlugin } from "@remcovaes/web-test-runner-vite-plugin"; +import { playwrightLauncher } from "@web/test-runner-playwright"; + +const LOG_FILTER = ["Lit is in dev mode", "[vite] connecting..."]; + +const getBrowser = (product) => + playwrightLauncher({ + product, + createBrowserContext: ({ browser }) => { + return browser.newContext({ permissions: ["clipboard-read"] }); + }, + }); + +export default { + coverage: true, + coverageConfig: { + include: ["src/**/*.{ts,tsx}", "src/**/**/*.{ts,tsx}"], + }, + files: ["src/test/**/*.js", "src/test/**/*.ts", "src/index.js"], + nodeResolve: true, + plugins: [vitePlugin()], + browsers: [getBrowser("chromium")], + filterBrowserLogs: ({ args }) => { + return !args.some((log) => { + return ( + typeof log === "string" && + LOG_FILTER.some((filterTerm) => log.includes(filterTerm)) + ); + }); + }, + testFramework: { + config: { + timeout: 1000, + }, + }, +};